1use std::cell::Cell;
6use std::convert::TryInto;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, QualName, local_name, ns};
10use js::rust::HandleObject;
11use style::str::{split_html_space_chars, str_join};
12use stylo_dom::ElementState;
13
14use crate::dom::attr::Attr;
15use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
16use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
17use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElement_Binding::HTMLSelectElementMethods;
18use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
19use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
20use crate::dom::bindings::error::Fallible;
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::root::DomRoot;
23use crate::dom::bindings::str::DOMString;
24use crate::dom::characterdata::CharacterData;
25use crate::dom::document::Document;
26use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
27use crate::dom::html::htmlelement::HTMLElement;
28use crate::dom::html::htmlformelement::HTMLFormElement;
29use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
30use crate::dom::html::htmlscriptelement::HTMLScriptElement;
31use crate::dom::html::htmlselectelement::HTMLSelectElement;
32use crate::dom::node::{
33 BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeTraits, ShadowIncluding,
34 UnbindContext,
35};
36use crate::dom::text::Text;
37use crate::dom::types::DocumentFragment;
38use crate::dom::validation::Validatable;
39use crate::dom::validitystate::ValidationFlags;
40use crate::dom::virtualmethods::VirtualMethods;
41use crate::dom::window::Window;
42use crate::script_runtime::CanGc;
43
44#[dom_struct]
45pub(crate) struct HTMLOptionElement {
46 htmlelement: HTMLElement,
47
48 selectedness: Cell<bool>,
50
51 dirtiness: Cell<bool>,
53}
54
55impl HTMLOptionElement {
56 fn new_inherited(
57 local_name: LocalName,
58 prefix: Option<Prefix>,
59 document: &Document,
60 ) -> HTMLOptionElement {
61 HTMLOptionElement {
62 htmlelement: HTMLElement::new_inherited_with_state(
63 ElementState::ENABLED,
64 local_name,
65 prefix,
66 document,
67 ),
68 selectedness: Cell::new(false),
69 dirtiness: Cell::new(false),
70 }
71 }
72
73 pub(crate) fn new(
74 local_name: LocalName,
75 prefix: Option<Prefix>,
76 document: &Document,
77 proto: Option<HandleObject>,
78 can_gc: CanGc,
79 ) -> DomRoot<HTMLOptionElement> {
80 Node::reflect_node_with_proto(
81 Box::new(HTMLOptionElement::new_inherited(
82 local_name, prefix, document,
83 )),
84 document,
85 proto,
86 can_gc,
87 )
88 }
89
90 pub(crate) fn set_selectedness(&self, selected: bool) {
91 self.selectedness.set(selected);
92 }
93
94 pub(crate) fn set_dirtiness(&self, dirtiness: bool) {
95 self.dirtiness.set(dirtiness);
96 }
97
98 fn pick_if_selected_and_reset(&self) {
99 if let Some(select) = self.owner_select_element() {
100 if self.Selected() {
101 select.pick_option(self);
102 }
103 select.ask_for_reset();
104 }
105 }
106
107 fn index(&self) -> i32 {
109 let Some(owner_select) = self.owner_select_element() else {
110 return 0;
111 };
112
113 let Some(position) = owner_select.list_of_options().position(|n| &*n == self) else {
114 warn!("HTMLOptionElement called index_in_select at a select that did not contain it");
116 return 0;
117 };
118
119 position.try_into().unwrap_or(0)
120 }
121
122 fn owner_select_element(&self) -> Option<DomRoot<HTMLSelectElement>> {
123 let parent = self.upcast::<Node>().GetParentNode()?;
124
125 if parent.is::<HTMLOptGroupElement>() {
126 DomRoot::downcast::<HTMLSelectElement>(parent.GetParentNode()?)
127 } else {
128 DomRoot::downcast::<HTMLSelectElement>(parent)
129 }
130 }
131
132 fn update_select_validity(&self, can_gc: CanGc) {
133 if let Some(select) = self.owner_select_element() {
134 select
135 .validity_state(can_gc)
136 .perform_validation_and_update(ValidationFlags::all(), can_gc);
137 }
138 }
139
140 pub(crate) fn displayed_label(&self) -> DOMString {
144 let label = self
147 .upcast::<Element>()
148 .get_string_attribute(&local_name!("label"));
149
150 if label.is_empty() {
151 return self.Text();
152 }
153
154 label
155 }
156
157 fn nearest_ancestor_select(&self) -> Option<DomRoot<HTMLSelectElement>> {
159 let mut did_see_ancestor_optgroup = false;
162
163 for ancestor in self
165 .upcast::<Node>()
166 .ancestors()
167 .filter_map(DomRoot::downcast::<Element>)
168 {
169 if matches!(
171 ancestor.local_name(),
172 &local_name!("datalist") | &local_name!("hr") | &local_name!("option")
173 ) {
174 return None;
175 }
176
177 if ancestor.local_name() == &local_name!("optgroup") {
179 if did_see_ancestor_optgroup {
181 return None;
182 }
183
184 did_see_ancestor_optgroup = true;
186 }
187
188 if let Some(select) = DomRoot::downcast::<HTMLSelectElement>(ancestor) {
190 return Some(select);
191 }
192 }
193
194 None
196 }
197
198 pub(crate) fn maybe_clone_an_option_into_selectedcontent(&self, can_gc: CanGc) {
200 let select = self.nearest_ancestor_select();
202
203 if self.selectedness.get() {
209 if let Some(selectedcontent) =
210 select.and_then(|select| select.get_enabled_selectedcontent())
211 {
212 self.clone_an_option_into_selectedcontent(&selectedcontent, can_gc);
213 }
214 }
215 }
216
217 fn clone_an_option_into_selectedcontent(&self, selectedcontent: &Element, can_gc: CanGc) {
219 let document_fragment = DocumentFragment::new(&self.owner_document(), can_gc);
221
222 for child in self.upcast::<Node>().children() {
224 let child_clone =
226 Node::clone(&child, None, CloneChildrenFlag::CloneChildren, None, can_gc);
227
228 let _ = document_fragment
230 .upcast::<Node>()
231 .AppendChild(&child_clone, can_gc);
232 }
233
234 Node::replace_all(
236 Some(document_fragment.upcast()),
237 selectedcontent.upcast(),
238 can_gc,
239 );
240 }
241}
242
243impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
244 fn Option(
246 window: &Window,
247 proto: Option<HandleObject>,
248 can_gc: CanGc,
249 text: DOMString,
250 value: Option<DOMString>,
251 default_selected: bool,
252 selected: bool,
253 ) -> Fallible<DomRoot<HTMLOptionElement>> {
254 let element = Element::create(
255 QualName::new(None, ns!(html), local_name!("option")),
256 None,
257 &window.Document(),
258 ElementCreator::ScriptCreated,
259 CustomElementCreationMode::Synchronous,
260 proto,
261 can_gc,
262 );
263
264 let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap();
265
266 if !text.is_empty() {
267 option
268 .upcast::<Node>()
269 .set_text_content_for_element(Some(text), can_gc)
270 }
271
272 if let Some(val) = value {
273 option.SetValue(val)
274 }
275
276 option.SetDefaultSelected(default_selected);
277 option.set_selectedness(selected);
278 option.update_select_validity(can_gc);
279 Ok(option)
280 }
281
282 make_bool_getter!(Disabled, "disabled");
284
285 make_bool_setter!(SetDisabled, "disabled");
287
288 fn Text(&self) -> DOMString {
290 let mut content = DOMString::new();
291
292 let mut iterator = self.upcast::<Node>().traverse_preorder(ShadowIncluding::No);
293 while let Some(node) = iterator.peek() {
294 if let Some(element) = node.downcast::<Element>() {
295 let html_script = element.is::<HTMLScriptElement>();
296 let svg_script = *element.namespace() == ns!(svg) &&
297 element.local_name() == &local_name!("script");
298 if html_script || svg_script {
299 iterator.next_skipping_children();
300 continue;
301 }
302 }
303
304 if node.is::<Text>() {
305 let characterdata = node.downcast::<CharacterData>().unwrap();
306 content.push_str(&characterdata.Data().str());
307 }
308
309 iterator.next();
310 }
311
312 DOMString::from(str_join(split_html_space_chars(&content.str()), " "))
313 }
314
315 fn SetText(&self, value: DOMString, can_gc: CanGc) {
317 self.upcast::<Node>()
318 .set_text_content_for_element(Some(value), can_gc)
319 }
320
321 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
323 let parent = self.upcast::<Node>().GetParentNode().and_then(|p| {
324 if p.is::<HTMLOptGroupElement>() {
325 p.upcast::<Node>().GetParentNode()
326 } else {
327 Some(p)
328 }
329 });
330
331 parent.and_then(|p| p.downcast::<HTMLSelectElement>().and_then(|s| s.GetForm()))
332 }
333
334 fn Value(&self) -> DOMString {
336 let element = self.upcast::<Element>();
337 let attr = &local_name!("value");
338 if element.has_attribute(attr) {
339 element.get_string_attribute(attr)
340 } else {
341 self.Text()
342 }
343 }
344
345 make_setter!(SetValue, "value");
347
348 fn Label(&self) -> DOMString {
350 let element = self.upcast::<Element>();
351 let attr = &local_name!("label");
352 if element.has_attribute(attr) {
353 element.get_string_attribute(attr)
354 } else {
355 self.Text()
356 }
357 }
358
359 make_setter!(SetLabel, "label");
361
362 make_bool_getter!(DefaultSelected, "selected");
364
365 make_bool_setter!(SetDefaultSelected, "selected");
367
368 fn Selected(&self) -> bool {
370 self.selectedness.get()
371 }
372
373 fn SetSelected(&self, selected: bool, can_gc: CanGc) {
375 self.dirtiness.set(true);
376 self.selectedness.set(selected);
377 self.pick_if_selected_and_reset();
378 self.update_select_validity(can_gc);
379 }
380
381 fn Index(&self) -> i32 {
383 self.index()
384 }
385}
386
387impl VirtualMethods for HTMLOptionElement {
388 fn super_type(&self) -> Option<&dyn VirtualMethods> {
389 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
390 }
391
392 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
393 self.super_type()
394 .unwrap()
395 .attribute_mutated(attr, mutation, can_gc);
396 match *attr.local_name() {
397 local_name!("disabled") => {
398 let el = self.upcast::<Element>();
399 match mutation {
400 AttributeMutation::Set(..) => {
401 el.set_disabled_state(true);
402 el.set_enabled_state(false);
403 },
404 AttributeMutation::Removed => {
405 el.set_disabled_state(false);
406 el.set_enabled_state(true);
407 el.check_parent_disabled_state_for_option();
408 },
409 }
410 self.update_select_validity(can_gc);
411 },
412 local_name!("selected") => {
413 match mutation {
414 AttributeMutation::Set(..) => {
415 if !self.dirtiness.get() {
417 self.selectedness.set(true);
418 }
419 },
420 AttributeMutation::Removed => {
421 if !self.dirtiness.get() {
423 self.selectedness.set(false);
424 }
425 },
426 }
427 self.update_select_validity(can_gc);
428 },
429 local_name!("label") => {
430 if let Some(select_element) = self.owner_select_element() {
433 select_element.update_shadow_tree(CanGc::note());
434 }
435 },
436 _ => {},
437 }
438 }
439
440 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
441 if let Some(s) = self.super_type() {
442 s.bind_to_tree(context, can_gc);
443 }
444
445 self.upcast::<Element>()
446 .check_parent_disabled_state_for_option();
447
448 self.pick_if_selected_and_reset();
449 self.update_select_validity(can_gc);
450 }
451
452 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
453 self.super_type().unwrap().unbind_from_tree(context, can_gc);
454
455 if let Some(select) = context
456 .parent
457 .inclusive_ancestors(ShadowIncluding::No)
458 .find_map(DomRoot::downcast::<HTMLSelectElement>)
459 {
460 select
461 .validity_state(can_gc)
462 .perform_validation_and_update(ValidationFlags::all(), can_gc);
463 select.ask_for_reset();
464 }
465
466 let node = self.upcast::<Node>();
467 let el = self.upcast::<Element>();
468 if node.GetParentNode().is_some() {
469 el.check_parent_disabled_state_for_option();
470 } else {
471 el.check_disabled_attribute();
472 }
473 }
474
475 fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
476 if let Some(super_type) = self.super_type() {
477 super_type.children_changed(mutation, can_gc);
478 }
479
480 if !self
483 .upcast::<Element>()
484 .has_attribute(&local_name!("label"))
485 {
486 if let Some(owner_select) = self.owner_select_element() {
487 if owner_select
488 .selected_option()
489 .is_some_and(|selected_option| self == &*selected_option)
490 {
491 owner_select.update_shadow_tree(can_gc);
492 }
493 }
494 }
495 }
496}