Skip to main content

script/dom/html/
htmlselectelement.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::default::Default;
6use std::iter;
7
8use crate::dom::activation::Activatable;
9use crate::dom::element::attributes::storage::AttrRef;
10use crate::dom::iterators::ShadowIncluding;
11use script_bindings::cell::{DomRefCell, Ref};
12use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
13use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
14use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
15use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
16use crate::dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods;
17use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
18use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
19use crate::dom::bindings::codegen::GenericBindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
20use crate::dom::bindings::codegen::GenericBindings::HTMLOptGroupElementBinding::HTMLOptGroupElement_Binding::HTMLOptGroupElementMethods;
21use crate::dom::bindings::codegen::UnionTypes::{
22    HTMLElementOrLong, HTMLOptionElementOrHTMLOptGroupElement,
23};
24use crate::dom::bindings::error::ErrorResult;
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::refcounted::Trusted;
27use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
28use crate::dom::bindings::str::DOMString;
29use crate::dom::characterdata::CharacterData;
30use crate::dom::document::Document;
31use crate::dom::document_embedder_controls::ControlElement;
32use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
33use crate::dom::event::Event;
34use crate::dom::event::{EventBubbles, EventCancelable, EventComposed};
35use crate::dom::eventtarget::EventTarget;
36use crate::dom::html::htmlcollection::{CollectionFilter, CollectionSource, HTMLCollection};
37use crate::dom::html::htmlelement::HTMLElement;
38use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
39use crate::dom::html::htmlformelement::{FormControl, FormDatum, FormDatumValue, HTMLFormElement};
40use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
41use crate::dom::html::htmloptionelement::HTMLOptionElement;
42use crate::dom::html::htmloptionscollection::HTMLOptionsCollection;
43use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeTraits,  UnbindContext};
44use crate::dom::nodelist::NodeList;
45use crate::dom::text::Text;
46use crate::dom::types::FocusEvent;
47use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable};
48use crate::dom::validitystate::{ValidationFlags, ValidityState};
49use crate::dom::virtualmethods::VirtualMethods;
50use crate::script_runtime::CanGc;
51use dom_struct::dom_struct;
52use embedder_traits::{EmbedderControlRequest, SelectElementRequest};
53use embedder_traits::{SelectElementOption, SelectElementOptionOrOptgroup};
54use html5ever::{local_name, ns, LocalName, Prefix, QualName};
55use js::context::JSContext;
56use js::rust::HandleObject;
57use style::attr::AttrValue;
58use stylo_dom::ElementState;
59
60const DEFAULT_SELECT_SIZE: u32 = 0;
61
62const SELECT_BOX_STYLE: &str = "
63    display: flex;
64    align-items: center;
65    height: 100%;
66    gap: 4px;
67";
68
69const TEXT_CONTAINER_STYLE: &str = "flex: 1;";
70
71const CHEVRON_CONTAINER_STYLE: &str = "
72    background-image: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"180\" height=\"180\" viewBox=\"0 0 180 180\"> <path d=\"M10 50h160L90 130z\" style=\"fill:currentcolor\"/> </svg>');
73    background-size: 100%;
74    background-repeat: no-repeat;
75    background-position: center;
76
77    vertical-align: middle;
78    line-height: 1;
79    display: inline-block;
80    width: 0.75em;
81    height: 0.75em;
82";
83
84#[derive(JSTraceable, MallocSizeOf)]
85struct OptionsFilter;
86impl CollectionFilter for OptionsFilter {
87    fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool {
88        if !elem.is::<HTMLOptionElement>() {
89            return false;
90        }
91
92        let node = elem.upcast::<Node>();
93        if root.is_parent_of(node) {
94            return true;
95        }
96
97        match node.GetParentNode() {
98            Some(optgroup) => optgroup.is::<HTMLOptGroupElement>() && root.is_parent_of(&optgroup),
99            None => false,
100        }
101    }
102}
103
104/// Provides selected options directly via [`HTMLSelectElement::list_of_options`],
105/// avoiding a full subtree traversal.
106#[derive(JSTraceable, MallocSizeOf)]
107struct SelectedOptionsSource;
108impl CollectionSource for SelectedOptionsSource {
109    fn iter<'a>(&'a self, root: &'a Node) -> Box<dyn Iterator<Item = DomRoot<Element>> + 'a> {
110        let select = root
111            .downcast::<HTMLSelectElement>()
112            .expect("SelectedOptionsSource must be rooted on an HTMLSelectElement");
113        Box::new(
114            select
115                .list_of_options()
116                .filter(|option| option.Selected())
117                .map(DomRoot::upcast::<Element>),
118        )
119    }
120}
121
122#[dom_struct]
123pub(crate) struct HTMLSelectElement {
124    htmlelement: HTMLElement,
125    options: MutNullableDom<HTMLOptionsCollection>,
126    selected_options: MutNullableDom<HTMLCollection>,
127    form_owner: MutNullableDom<HTMLFormElement>,
128    labels_node_list: MutNullableDom<NodeList>,
129    validity_state: MutNullableDom<ValidityState>,
130    shadow_tree: DomRefCell<Option<ShadowTree>>,
131}
132
133/// Holds handles to all elements in the UA shadow tree
134#[derive(Clone, JSTraceable, MallocSizeOf)]
135#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
136struct ShadowTree {
137    selected_option: Dom<Text>,
138}
139
140impl HTMLSelectElement {
141    fn new_inherited(
142        local_name: LocalName,
143        prefix: Option<Prefix>,
144        document: &Document,
145    ) -> HTMLSelectElement {
146        HTMLSelectElement {
147            htmlelement: HTMLElement::new_inherited_with_state(
148                ElementState::ENABLED | ElementState::VALID,
149                local_name,
150                prefix,
151                document,
152            ),
153            options: Default::default(),
154            selected_options: Default::default(),
155            form_owner: Default::default(),
156            labels_node_list: Default::default(),
157            validity_state: Default::default(),
158            shadow_tree: Default::default(),
159        }
160    }
161
162    pub(crate) fn new(
163        cx: &mut js::context::JSContext,
164        local_name: LocalName,
165        prefix: Option<Prefix>,
166        document: &Document,
167        proto: Option<HandleObject>,
168    ) -> DomRoot<HTMLSelectElement> {
169        let n = Node::reflect_node_with_proto(
170            cx,
171            Box::new(HTMLSelectElement::new_inherited(
172                local_name, prefix, document,
173            )),
174            document,
175            proto,
176        );
177
178        n.upcast::<Node>().set_weird_parser_insertion_mode();
179        n
180    }
181
182    /// <https://html.spec.whatwg.org/multipage/#concept-select-option-list>
183    pub(crate) fn list_of_options(
184        &self,
185    ) -> impl Iterator<Item = DomRoot<HTMLOptionElement>> + use<'_> {
186        self.upcast::<Node>().children().flat_map(|node| {
187            if node.is::<HTMLOptionElement>() {
188                let node = DomRoot::downcast::<HTMLOptionElement>(node).unwrap();
189                Choice3::First(iter::once(node))
190            } else if node.is::<HTMLOptGroupElement>() {
191                Choice3::Second(node.children().filter_map(DomRoot::downcast))
192            } else {
193                Choice3::Third(iter::empty())
194            }
195        })
196    }
197
198    /// <https://html.spec.whatwg.org/multipage/#placeholder-label-option>
199    fn get_placeholder_label_option(&self) -> Option<DomRoot<HTMLOptionElement>> {
200        if self.Required() && !self.Multiple() && self.display_size() == 1 {
201            self.list_of_options().next().filter(|node| {
202                let parent = node.upcast::<Node>().GetParentNode();
203                node.Value().is_empty() && parent.as_deref() == Some(self.upcast())
204            })
205        } else {
206            None
207        }
208    }
209
210    // https://html.spec.whatwg.org/multipage/#the-select-element:concept-form-reset-control
211    pub(crate) fn reset(&self) {
212        for opt in self.list_of_options() {
213            opt.set_selectedness(opt.DefaultSelected());
214            opt.set_dirtiness(false);
215        }
216        self.ask_for_reset();
217    }
218
219    // https://html.spec.whatwg.org/multipage/#ask-for-a-reset
220    pub(crate) fn ask_for_reset(&self) {
221        if self.Multiple() {
222            return;
223        }
224
225        let mut first_enabled: Option<DomRoot<HTMLOptionElement>> = None;
226        let mut last_selected: Option<DomRoot<HTMLOptionElement>> = None;
227
228        for opt in self.list_of_options() {
229            if opt.Selected() {
230                opt.set_selectedness(false);
231                last_selected = Some(DomRoot::from_ref(&opt));
232            }
233            let element = opt.upcast::<Element>();
234            if first_enabled.is_none() && !element.disabled_state() {
235                first_enabled = Some(DomRoot::from_ref(&opt));
236            }
237        }
238
239        if let Some(last_selected) = last_selected {
240            last_selected.set_selectedness(true);
241        } else if self.display_size() == 1 &&
242            let Some(first_enabled) = first_enabled
243        {
244            first_enabled.set_selectedness(true);
245        }
246    }
247
248    pub(crate) fn push_form_data(&self, data_set: &mut Vec<FormDatum>) {
249        if self.Name().is_empty() {
250            return;
251        }
252        for opt in self.list_of_options() {
253            let element = opt.upcast::<Element>();
254            if opt.Selected() && element.enabled_state() {
255                data_set.push(FormDatum {
256                    ty: self.Type(),
257                    name: self.Name(),
258                    value: FormDatumValue::String(opt.Value()),
259                });
260            }
261        }
262    }
263
264    // https://html.spec.whatwg.org/multipage/#concept-select-pick
265    pub(crate) fn pick_option(&self, picked: &HTMLOptionElement) {
266        if !self.Multiple() {
267            let picked = picked.upcast();
268            for opt in self.list_of_options() {
269                if opt.upcast::<HTMLElement>() != picked {
270                    opt.set_selectedness(false);
271                }
272            }
273        }
274    }
275
276    /// <https://html.spec.whatwg.org/multipage/#concept-select-size>
277    fn display_size(&self) -> u32 {
278        if self.Size() == 0 {
279            if self.Multiple() { 4 } else { 1 }
280        } else {
281            self.Size()
282        }
283    }
284
285    fn create_shadow_tree(&self, cx: &mut JSContext) {
286        let document = self.owner_document();
287        let root = self.upcast::<Element>().attach_ua_shadow_root(cx, true);
288
289        let select_box = Element::create(
290            cx,
291            QualName::new(None, ns!(html), local_name!("div")),
292            None,
293            &document,
294            ElementCreator::ScriptCreated,
295            CustomElementCreationMode::Asynchronous,
296            None,
297        );
298        select_box.set_string_attribute(cx, &local_name!("style"), SELECT_BOX_STYLE.into());
299
300        let text_container = Element::create(
301            cx,
302            QualName::new(None, ns!(html), local_name!("div")),
303            None,
304            &document,
305            ElementCreator::ScriptCreated,
306            CustomElementCreationMode::Asynchronous,
307            None,
308        );
309        text_container.set_string_attribute(cx, &local_name!("style"), TEXT_CONTAINER_STYLE.into());
310        select_box
311            .upcast::<Node>()
312            .AppendChild(cx, text_container.upcast::<Node>())
313            .unwrap();
314
315        let text = Text::new(cx, DOMString::new(), &document);
316        let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
317            selected_option: text.as_traced(),
318        });
319        text_container
320            .upcast::<Node>()
321            .AppendChild(cx, text.upcast::<Node>())
322            .unwrap();
323
324        let chevron_container = Element::create(
325            cx,
326            QualName::new(None, ns!(html), local_name!("div")),
327            None,
328            &document,
329            ElementCreator::ScriptCreated,
330            CustomElementCreationMode::Asynchronous,
331            None,
332        );
333        chevron_container.set_string_attribute(
334            cx,
335            &local_name!("style"),
336            CHEVRON_CONTAINER_STYLE.into(),
337        );
338        select_box
339            .upcast::<Node>()
340            .AppendChild(cx, chevron_container.upcast::<Node>())
341            .unwrap();
342
343        root.upcast::<Node>()
344            .AppendChild(cx, select_box.upcast::<Node>())
345            .unwrap();
346    }
347
348    fn shadow_tree(&self, cx: &mut JSContext) -> Ref<'_, ShadowTree> {
349        if !self.upcast::<Element>().is_shadow_host() {
350            self.create_shadow_tree(cx);
351        }
352
353        Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref)
354            .ok()
355            .expect("UA shadow tree was not created")
356    }
357
358    pub(crate) fn update_shadow_tree(&self, cx: &mut JSContext) {
359        let shadow_tree = self.shadow_tree(cx);
360
361        let selected_options = self.selected_options();
362        let selected_options_count = selected_options.len();
363
364        let displayed_text = if selected_options_count == 1 {
365            let first_selected_option = self
366                .selected_option()
367                .or_else(|| self.list_of_options().next());
368
369            let first_selected_option_text = first_selected_option
370                .map(|option| option.displayed_label())
371                .unwrap_or_default();
372
373            // Replace newlines with whitespace, then collapse and trim whitespace
374            itertools::join(first_selected_option_text.str().split_whitespace(), " ")
375        } else {
376            format!("{selected_options_count} selected")
377        };
378
379        shadow_tree
380            .selected_option
381            .upcast::<CharacterData>()
382            .SetData(cx, displayed_text.trim().into());
383    }
384
385    pub(crate) fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> {
386        self.list_of_options()
387            .find(|opt_elem| opt_elem.Selected())
388            .or_else(|| self.list_of_options().next())
389    }
390
391    pub(crate) fn selected_options(&self) -> Vec<DomRoot<HTMLOptionElement>> {
392        self.list_of_options()
393            .filter(|opt_elem| opt_elem.Selected())
394            .collect()
395    }
396
397    pub(crate) fn show_menu(&self) {
398        // Collect list of optgroups and options
399        let mut index = 0;
400        let mut embedder_option_from_option = |option: &HTMLOptionElement| {
401            let embedder_option = SelectElementOption {
402                id: index,
403                label: option.displayed_label().into(),
404                is_disabled: option.Disabled(),
405            };
406            index += 1;
407            embedder_option
408        };
409        let options = self
410            .upcast::<Node>()
411            .children()
412            .flat_map(|child| {
413                if let Some(option) = child.downcast::<HTMLOptionElement>() {
414                    return Some(embedder_option_from_option(option).into());
415                }
416
417                if let Some(optgroup) = child.downcast::<HTMLOptGroupElement>() {
418                    let options = optgroup
419                        .upcast::<Node>()
420                        .children()
421                        .flat_map(DomRoot::downcast::<HTMLOptionElement>)
422                        .map(|option| embedder_option_from_option(&option))
423                        .collect();
424                    let label = optgroup.Label().into();
425
426                    return Some(SelectElementOptionOrOptgroup::Optgroup { label, options });
427                }
428
429                None
430            })
431            .collect();
432
433        let selected_options = self
434            .list_of_options()
435            .enumerate()
436            .filter(|(_, option)| option.Selected())
437            .map(|(index, _)| index)
438            .collect();
439
440        self.owner_document()
441            .embedder_controls()
442            .show_embedder_control(
443                ControlElement::Select(DomRoot::from_ref(self)),
444                EmbedderControlRequest::SelectElement(SelectElementRequest {
445                    options,
446                    selected_options,
447                    allow_select_multiple: self.Multiple(),
448                }),
449                None,
450            );
451        self.upcast::<Element>().set_open_state(true);
452    }
453
454    pub(crate) fn handle_embedder_response(&self, cx: &mut JSContext, selected_values: Vec<usize>) {
455        self.upcast::<Element>().set_open_state(false);
456
457        let selected_values = if self.Multiple() {
458            selected_values
459        } else {
460            selected_values.into_iter().take(1).collect()
461        };
462
463        let mut selection_did_change = false;
464        for (index, option) in self.list_of_options().enumerate() {
465            let should_be_selected = selected_values.contains(&index);
466            let option_selected_did_change = option.Selected() != should_be_selected;
467
468            if option_selected_did_change {
469                selection_did_change = true;
470            }
471
472            option.set_selectedness(should_be_selected);
473
474            if option_selected_did_change {
475                option.set_dirtiness(true);
476            }
477        }
478
479        if selection_did_change {
480            self.update_shadow_tree(cx);
481            self.send_update_notifications();
482        }
483    }
484
485    fn multiple_attribute_mutated(&self, cx: &mut JSContext, mutation: AttributeMutation) {
486        if mutation.is_removal() {
487            let mut first_enabled: Option<DomRoot<HTMLOptionElement>> = None;
488            let mut first_selected: Option<DomRoot<HTMLOptionElement>> = None;
489
490            for option in self.list_of_options() {
491                if first_selected.is_none() && option.Selected() {
492                    first_selected = Some(DomRoot::from_ref(&option));
493                }
494                option.set_selectedness(false);
495                let element = option.upcast::<Element>();
496                if first_enabled.is_none() && !element.disabled_state() {
497                    first_enabled = Some(DomRoot::from_ref(&option));
498                }
499            }
500
501            if let Some(first_selected) = first_selected {
502                first_selected.set_selectedness(true);
503            } else if self.display_size() == 1 &&
504                let Some(first_enabled) = first_enabled
505            {
506                first_enabled.set_selectedness(true);
507            }
508
509            self.update_shadow_tree(cx);
510        }
511    }
512
513    /// <https://html.spec.whatwg.org/multipage/#send-select-update-notifications>
514    fn send_update_notifications(&self) {
515        // > When the user agent is to send select update notifications, queue an element task on the
516        // > user interaction task source given the select element to run these steps:
517        let this = Trusted::new(self);
518        self.owner_global()
519            .task_manager()
520            .user_interaction_task_source()
521            .queue(task!(send_select_update_notification: move |cx| {
522                let this = this.root();
523
524                // TODO: Step 1. Set the select element's user validity to true.
525
526                // Step 2. Fire an event named input at the select element, with the bubbles and composed
527                // attributes initialized to true.
528                this.upcast::<EventTarget>()
529                    .fire_event_with_params(cx,
530                        atom!("input"),
531                        EventBubbles::Bubbles,
532                        EventCancelable::NotCancelable,
533                        EventComposed::Composed,
534                    );
535
536                // Step 3. Fire an event named change at the select element, with the bubbles attribute initialized
537                // to true.
538                this.upcast::<EventTarget>()
539                    .fire_bubbling_event(cx, atom!("change"));
540            }));
541    }
542
543    fn may_have_embedder_control(&self) -> bool {
544        let el = self.upcast::<Element>();
545        !el.disabled_state()
546    }
547
548    /// <https://html.spec.whatwg.org/multipage/#select-enabled-selectedcontent>
549    pub(crate) fn get_enabled_selectedcontent(&self) -> Option<DomRoot<Element>> {
550        // Step 1. If select has the multiple attribute, then return null.
551        if self.Multiple() {
552            return None;
553        }
554
555        // Step 2. Let selectedcontent be the first selectedcontent element descendant
556        // of select in tree order if any such element exists; otherwise return null.
557        // TODO: Step 3. If selectedcontent's disabled is true, then return null.
558        // NOTE: We don't actually implement selectedcontent yet
559        // Step 4. Return selectedcontent.
560        self.upcast::<Node>()
561            .traverse_preorder(ShadowIncluding::No)
562            .skip(1)
563            .filter_map(DomRoot::downcast::<Element>)
564            .find(|element| element.local_name() == &local_name!("selectedcontent"))
565    }
566}
567
568impl HTMLSelectElementMethods<crate::DomTypeHolder> for HTMLSelectElement {
569    /// <https://html.spec.whatwg.org/multipage/#dom-select-add>
570    fn Add(
571        &self,
572        cx: &mut JSContext,
573        element: HTMLOptionElementOrHTMLOptGroupElement,
574        before: Option<HTMLElementOrLong>,
575    ) -> ErrorResult {
576        self.Options().Add(cx, element, before)
577    }
578
579    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
580    make_bool_getter!(Disabled, "disabled");
581
582    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
583    make_bool_setter!(SetDisabled, "disabled");
584
585    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
586    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
587        self.form_owner()
588    }
589
590    // https://html.spec.whatwg.org/multipage/#dom-select-multiple
591    make_bool_getter!(Multiple, "multiple");
592
593    // https://html.spec.whatwg.org/multipage/#dom-select-multiple
594    make_bool_setter!(SetMultiple, "multiple");
595
596    // https://html.spec.whatwg.org/multipage/#dom-fe-name
597    make_getter!(Name, "name");
598
599    // https://html.spec.whatwg.org/multipage/#dom-fe-name
600    make_atomic_setter!(SetName, "name");
601
602    // https://html.spec.whatwg.org/multipage/#dom-select-required
603    make_bool_getter!(Required, "required");
604
605    // https://html.spec.whatwg.org/multipage/#dom-select-required
606    make_bool_setter!(SetRequired, "required");
607
608    // https://html.spec.whatwg.org/multipage/#dom-select-size
609    make_uint_getter!(Size, "size", DEFAULT_SELECT_SIZE);
610
611    // https://html.spec.whatwg.org/multipage/#dom-select-size
612    make_uint_setter!(SetSize, "size", DEFAULT_SELECT_SIZE);
613
614    /// <https://html.spec.whatwg.org/multipage/#dom-select-type>
615    fn Type(&self) -> DOMString {
616        DOMString::from(if self.Multiple() {
617            "select-multiple"
618        } else {
619            "select-one"
620        })
621    }
622
623    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
624    make_labels_getter!(Labels, labels_node_list);
625
626    /// <https://html.spec.whatwg.org/multipage/#dom-select-options>
627    fn Options(&self) -> DomRoot<HTMLOptionsCollection> {
628        self.options.or_init(|| {
629            let window = self.owner_window();
630            HTMLOptionsCollection::new(
631                &window,
632                self,
633                Box::new(OptionsFilter),
634                CanGc::deprecated_note(),
635            )
636        })
637    }
638
639    /// <https://html.spec.whatwg.org/multipage/#dom-select-selectedoptions>
640    fn SelectedOptions(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
641        self.selected_options.or_init(|| {
642            let window = self.owner_window();
643            HTMLCollection::new_with_source(
644                cx,
645                &window,
646                self.upcast(),
647                Box::new(SelectedOptionsSource),
648            )
649        })
650    }
651
652    /// <https://html.spec.whatwg.org/multipage/#dom-select-length>
653    fn Length(&self) -> u32 {
654        self.Options().Length()
655    }
656
657    /// <https://html.spec.whatwg.org/multipage/#dom-select-length>
658    fn SetLength(&self, cx: &mut JSContext, length: u32) {
659        self.Options().SetLength(cx, length)
660    }
661
662    /// <https://html.spec.whatwg.org/multipage/#dom-select-item>
663    fn Item(&self, index: u32) -> Option<DomRoot<Element>> {
664        self.Options().upcast().Item(index)
665    }
666
667    /// <https://html.spec.whatwg.org/multipage/#dom-select-item>
668    fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
669        self.Options().IndexedGetter(index)
670    }
671
672    /// <https://html.spec.whatwg.org/multipage/#dom-select-setter>
673    fn IndexedSetter(
674        &self,
675        cx: &mut JSContext,
676        index: u32,
677        value: Option<&HTMLOptionElement>,
678    ) -> ErrorResult {
679        self.Options().IndexedSetter(cx, index, value)
680    }
681
682    /// <https://html.spec.whatwg.org/multipage/#dom-select-nameditem>
683    fn NamedItem(&self, name: DOMString) -> Option<DomRoot<HTMLOptionElement>> {
684        self.Options()
685            .NamedGetter(name)
686            .and_then(DomRoot::downcast::<HTMLOptionElement>)
687    }
688
689    /// <https://html.spec.whatwg.org/multipage/#dom-select-remove>
690    fn Remove_(&self, cx: &mut JSContext, index: i32) {
691        self.Options().Remove(cx, index)
692    }
693
694    /// <https://html.spec.whatwg.org/multipage/#dom-select-remove>
695    fn Remove(&self, cx: &mut JSContext) {
696        self.upcast::<Element>().Remove(cx)
697    }
698
699    /// <https://html.spec.whatwg.org/multipage/#dom-select-value>
700    fn Value(&self) -> DOMString {
701        self.list_of_options()
702            .find(|opt_elem| opt_elem.Selected())
703            .map(|opt_elem| opt_elem.Value())
704            .unwrap_or_default()
705    }
706
707    /// <https://html.spec.whatwg.org/multipage/#dom-select-value>
708    fn SetValue(&self, cx: &mut JSContext, value: DOMString) {
709        let mut opt_iter = self.list_of_options();
710        // Reset until we find an <option> with a matching value
711        for opt in opt_iter.by_ref() {
712            if opt.Value() == value {
713                opt.set_selectedness(true);
714                opt.set_dirtiness(true);
715                break;
716            }
717            opt.set_selectedness(false);
718        }
719        // Reset remaining <option> elements
720        for opt in opt_iter {
721            opt.set_selectedness(false);
722        }
723
724        self.validity_state(cx)
725            .perform_validation_and_update(cx, ValidationFlags::VALUE_MISSING);
726    }
727
728    /// <https://html.spec.whatwg.org/multipage/#dom-select-selectedindex>
729    fn SelectedIndex(&self) -> i32 {
730        self.list_of_options()
731            .enumerate()
732            .filter(|(_, opt_elem)| opt_elem.Selected())
733            .map(|(i, _)| i as i32)
734            .next()
735            .unwrap_or(-1)
736    }
737
738    /// <https://html.spec.whatwg.org/multipage/#dom-select-selectedindex>
739    fn SetSelectedIndex(&self, cx: &mut JSContext, index: i32) {
740        let mut selection_did_change = false;
741
742        let mut opt_iter = self.list_of_options();
743        for opt in opt_iter.by_ref().take(index as usize) {
744            selection_did_change |= opt.Selected();
745            opt.set_selectedness(false);
746        }
747        if let Some(selected_option) = opt_iter.next() {
748            selection_did_change |= !selected_option.Selected();
749            selected_option.set_selectedness(true);
750            selected_option.set_dirtiness(true);
751
752            // Reset remaining <option> elements
753            for opt in opt_iter {
754                selection_did_change |= opt.Selected();
755                opt.set_selectedness(false);
756            }
757        }
758
759        if selection_did_change {
760            self.update_shadow_tree(cx);
761        }
762    }
763
764    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
765    fn WillValidate(&self) -> bool {
766        self.is_instance_validatable()
767    }
768
769    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
770    fn Validity(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
771        self.validity_state(cx)
772    }
773
774    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
775    fn CheckValidity(&self, cx: &mut JSContext) -> bool {
776        self.check_validity(cx)
777    }
778
779    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
780    fn ReportValidity(&self, cx: &mut JSContext) -> bool {
781        self.report_validity(cx)
782    }
783
784    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
785    fn ValidationMessage(&self, cx: &mut JSContext) -> DOMString {
786        self.validation_message(cx)
787    }
788
789    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
790    fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
791        self.validity_state(cx).set_custom_error_message(cx, error);
792    }
793}
794
795impl VirtualMethods for HTMLSelectElement {
796    fn super_type(&self) -> Option<&dyn VirtualMethods> {
797        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
798    }
799
800    fn attribute_mutated(
801        &self,
802        cx: &mut js::context::JSContext,
803        attr: AttrRef<'_>,
804        mutation: AttributeMutation,
805    ) {
806        let could_have_had_embedder_control = self.may_have_embedder_control();
807        self.super_type()
808            .unwrap()
809            .attribute_mutated(cx, attr, mutation);
810        match *attr.local_name() {
811            local_name!("multiple") => {
812                self.multiple_attribute_mutated(cx, mutation);
813            },
814            local_name!("required") => {
815                self.validity_state(cx)
816                    .perform_validation_and_update(cx, ValidationFlags::VALUE_MISSING);
817            },
818            local_name!("disabled") => {
819                let el = self.upcast::<Element>();
820                match mutation {
821                    AttributeMutation::Set(..) => {
822                        el.set_disabled_state(true);
823                        el.set_enabled_state(false);
824                    },
825                    AttributeMutation::Removed => {
826                        el.set_disabled_state(false);
827                        el.set_enabled_state(true);
828                        el.check_ancestors_disabled_state_for_form_control();
829                    },
830                }
831
832                self.validity_state(cx)
833                    .perform_validation_and_update(cx, ValidationFlags::VALUE_MISSING);
834            },
835            local_name!("form") => {
836                self.form_attribute_mutated(cx, mutation);
837            },
838            _ => {},
839        }
840        if could_have_had_embedder_control && !self.may_have_embedder_control() {
841            self.owner_document()
842                .embedder_controls()
843                .hide_embedder_control(self.upcast());
844        }
845    }
846
847    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
848        if let Some(s) = self.super_type() {
849            s.bind_to_tree(cx, context);
850        }
851
852        self.upcast::<Element>()
853            .check_ancestors_disabled_state_for_form_control();
854    }
855
856    fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
857        self.super_type().unwrap().unbind_from_tree(cx, context);
858
859        let node = self.upcast::<Node>();
860        let el = self.upcast::<Element>();
861        if node
862            .ancestors()
863            .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
864        {
865            el.check_ancestors_disabled_state_for_form_control();
866        } else {
867            el.check_disabled_attribute();
868        }
869
870        self.owner_document()
871            .embedder_controls()
872            .hide_embedder_control(self.upcast());
873    }
874
875    fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
876        if let Some(s) = self.super_type() {
877            s.children_changed(cx, mutation);
878        }
879
880        self.update_shadow_tree(cx);
881    }
882
883    fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
884        match *local_name {
885            local_name!("size") => AttrValue::from_u32(value.into(), DEFAULT_SELECT_SIZE),
886            _ => self
887                .super_type()
888                .unwrap()
889                .parse_plain_attribute(local_name, value),
890        }
891    }
892
893    fn handle_event(&self, cx: &mut js::context::JSContext, event: &Event) {
894        self.super_type().unwrap().handle_event(cx, event);
895        if let Some(event) = event.downcast::<FocusEvent>() &&
896            *event.upcast::<Event>().type_() != *"blur"
897        {
898            self.owner_document()
899                .embedder_controls()
900                .hide_embedder_control(self.upcast());
901        }
902    }
903}
904
905impl FormControl for HTMLSelectElement {
906    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
907        self.form_owner.get()
908    }
909
910    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
911        self.form_owner.set(form);
912    }
913
914    fn to_element(&self) -> &Element {
915        self.upcast::<Element>()
916    }
917}
918
919impl Validatable for HTMLSelectElement {
920    fn as_element(&self) -> &Element {
921        self.upcast()
922    }
923
924    fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
925        self.validity_state
926            .or_init(|| ValidityState::new(cx, &self.owner_window(), self.upcast()))
927    }
928
929    fn is_instance_validatable(&self) -> bool {
930        // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
931        // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
932        !self.upcast::<Element>().disabled_state() && !is_barred_by_datalist_ancestor(self.upcast())
933    }
934
935    fn perform_validation(
936        &self,
937        _cx: &mut JSContext,
938        validate_flags: ValidationFlags,
939    ) -> ValidationFlags {
940        let mut failed_flags = ValidationFlags::empty();
941
942        // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
943        // https://html.spec.whatwg.org/multipage/#the-select-element%3Asuffering-from-being-missing
944        if validate_flags.contains(ValidationFlags::VALUE_MISSING) && self.Required() {
945            let placeholder = self.get_placeholder_label_option();
946            let is_value_missing = !self
947                .list_of_options()
948                .any(|e| e.Selected() && placeholder != Some(e));
949            failed_flags.set(ValidationFlags::VALUE_MISSING, is_value_missing);
950        }
951
952        failed_flags
953    }
954}
955
956impl Activatable for HTMLSelectElement {
957    fn as_element(&self) -> &Element {
958        self.upcast()
959    }
960
961    fn is_instance_activatable(&self) -> bool {
962        !self.upcast::<Element>().disabled_state()
963    }
964
965    fn activation_behavior(
966        &self,
967        _cx: &mut js::context::JSContext,
968        event: &Event,
969        _target: &EventTarget,
970    ) {
971        if !event.IsTrusted() {
972            return;
973        }
974
975        self.show_menu();
976    }
977}
978
979enum Choice3<I, J, K> {
980    First(I),
981    Second(J),
982    Third(K),
983}
984
985impl<I, J, K, T> Iterator for Choice3<I, J, K>
986where
987    I: Iterator<Item = T>,
988    J: Iterator<Item = T>,
989    K: Iterator<Item = T>,
990{
991    type Item = T;
992
993    fn next(&mut self) -> Option<T> {
994        match *self {
995            Choice3::First(ref mut i) => i.next(),
996            Choice3::Second(ref mut j) => j.next(),
997            Choice3::Third(ref mut k) => k.next(),
998        }
999    }
1000
1001    fn size_hint(&self) -> (usize, Option<usize>) {
1002        match *self {
1003            Choice3::First(ref i) => i.size_hint(),
1004            Choice3::Second(ref j) => j.size_hint(),
1005            Choice3::Third(ref k) => k.size_hint(),
1006        }
1007    }
1008}