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