Skip to main content

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