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