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