1use std::cell::Cell;
6use std::convert::TryInto;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, QualName, local_name, ns};
10use js::context::JSContext;
11use js::rust::HandleObject;
12use style::str::{split_html_space_chars, str_join};
13use stylo_dom::ElementState;
14
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::attributes::storage::AttrRef;
27use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
28use crate::dom::html::htmlelement::HTMLElement;
29use crate::dom::html::htmlformelement::HTMLFormElement;
30use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
31use crate::dom::html::htmlscriptelement::HTMLScriptElement;
32use crate::dom::html::htmlselectelement::HTMLSelectElement;
33use crate::dom::node::{
34 BindContext, ChildrenMutation, CloneChildrenFlag, MoveContext, Node, NodeTraits,
35 ShadowIncluding, UnbindContext,
36};
37use crate::dom::text::Text;
38use crate::dom::types::DocumentFragment;
39use crate::dom::validation::Validatable;
40use crate::dom::validitystate::ValidationFlags;
41use crate::dom::virtualmethods::VirtualMethods;
42use crate::dom::window::Window;
43use crate::script_runtime::CanGc;
44
45#[dom_struct]
46pub(crate) struct HTMLOptionElement {
47 htmlelement: HTMLElement,
48
49 selectedness: Cell<bool>,
51
52 dirtiness: Cell<bool>,
54}
55
56impl HTMLOptionElement {
57 fn new_inherited(
58 local_name: LocalName,
59 prefix: Option<Prefix>,
60 document: &Document,
61 ) -> HTMLOptionElement {
62 HTMLOptionElement {
63 htmlelement: HTMLElement::new_inherited_with_state(
64 ElementState::ENABLED,
65 local_name,
66 prefix,
67 document,
68 ),
69 selectedness: Cell::new(false),
70 dirtiness: Cell::new(false),
71 }
72 }
73
74 pub(crate) fn new(
75 cx: &mut js::context::JSContext,
76 local_name: LocalName,
77 prefix: Option<Prefix>,
78 document: &Document,
79 proto: Option<HandleObject>,
80 ) -> DomRoot<HTMLOptionElement> {
81 Node::reflect_node_with_proto(
82 cx,
83 Box::new(HTMLOptionElement::new_inherited(
84 local_name, prefix, document,
85 )),
86 document,
87 proto,
88 )
89 }
90
91 pub(crate) fn set_selectedness(&self, selected: bool) {
92 self.selectedness.set(selected);
93 self.upcast::<Node>().rev_version();
96 }
97
98 pub(crate) fn set_dirtiness(&self, dirtiness: bool) {
99 self.dirtiness.set(dirtiness);
100 }
101
102 fn pick_if_selected_and_reset(&self) {
103 if let Some(select) = self.owner_select_element() {
104 if self.Selected() {
105 select.pick_option(self);
106 }
107 select.ask_for_reset();
108 }
109 }
110
111 fn index(&self) -> i32 {
113 let Some(owner_select) = self.owner_select_element() else {
114 return 0;
115 };
116
117 let Some(position) = owner_select.list_of_options().position(|n| &*n == self) else {
118 warn!("HTMLOptionElement called index_in_select at a select that did not contain it");
120 return 0;
121 };
122
123 position.try_into().unwrap_or(0)
124 }
125
126 fn owner_select_element(&self) -> Option<DomRoot<HTMLSelectElement>> {
127 let parent = self.upcast::<Node>().GetParentNode()?;
128
129 if parent.is::<HTMLOptGroupElement>() {
130 DomRoot::downcast::<HTMLSelectElement>(parent.GetParentNode()?)
131 } else {
132 DomRoot::downcast::<HTMLSelectElement>(parent)
133 }
134 }
135
136 fn update_select_validity(&self, can_gc: CanGc) {
137 if let Some(select) = self.owner_select_element() {
138 select
139 .validity_state(can_gc)
140 .perform_validation_and_update(ValidationFlags::all(), can_gc);
141 }
142 }
143
144 pub(crate) fn displayed_label(&self) -> DOMString {
148 let label = self
151 .upcast::<Element>()
152 .get_string_attribute(&local_name!("label"));
153
154 if label.is_empty() {
155 return self.Text();
156 }
157
158 label
159 }
160
161 pub(crate) fn nearest_ancestor_select(&self) -> Option<DomRoot<HTMLSelectElement>> {
163 let mut did_see_ancestor_optgroup = false;
166
167 for ancestor in self
169 .upcast::<Node>()
170 .ancestors()
171 .filter_map(DomRoot::downcast::<Element>)
172 {
173 if matches!(
175 ancestor.local_name(),
176 &local_name!("datalist") | &local_name!("hr") | &local_name!("option")
177 ) {
178 return None;
179 }
180
181 if ancestor.local_name() == &local_name!("optgroup") {
183 if did_see_ancestor_optgroup {
185 return None;
186 }
187
188 did_see_ancestor_optgroup = true;
190 }
191
192 if let Some(select) = DomRoot::downcast::<HTMLSelectElement>(ancestor) {
194 return Some(select);
195 }
196 }
197
198 None
200 }
201
202 pub(crate) fn maybe_clone_an_option_into_selectedcontent(&self, cx: &mut JSContext) {
204 let select = self.nearest_ancestor_select();
206
207 if self.selectedness.get() &&
213 let Some(selectedcontent) =
214 select.and_then(|select| select.get_enabled_selectedcontent())
215 {
216 self.clone_an_option_into_selectedcontent(cx, &selectedcontent);
217 }
218 }
219
220 fn clone_an_option_into_selectedcontent(&self, cx: &mut JSContext, selectedcontent: &Element) {
222 let document_fragment = DocumentFragment::new(cx, &self.owner_document());
224
225 for child in self.upcast::<Node>().children() {
227 let child_clone = Node::clone(cx, &child, None, CloneChildrenFlag::CloneChildren, None);
229
230 let _ = document_fragment
232 .upcast::<Node>()
233 .AppendChild(cx, &child_clone);
234 }
235
236 Node::replace_all(
238 cx,
239 Some(document_fragment.upcast()),
240 selectedcontent.upcast(),
241 );
242 }
243}
244
245impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
246 fn Option(
248 cx: &mut JSContext,
249 window: &Window,
250 proto: Option<HandleObject>,
251 text: DOMString,
252 value: Option<DOMString>,
253 default_selected: bool,
254 selected: bool,
255 ) -> Fallible<DomRoot<HTMLOptionElement>> {
256 let element = Element::create(
257 cx,
258 QualName::new(None, ns!(html), local_name!("option")),
259 None,
260 &window.Document(),
261 ElementCreator::ScriptCreated,
262 CustomElementCreationMode::Synchronous,
263 proto,
264 );
265
266 let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap();
267
268 if !text.is_empty() {
269 option
270 .upcast::<Node>()
271 .set_text_content_for_element(cx, Some(text))
272 }
273
274 if let Some(val) = value {
275 option.SetValue(cx, val)
276 }
277
278 option.SetDefaultSelected(cx, default_selected);
279 option.set_selectedness(selected);
280 option.update_select_validity(CanGc::from_cx(cx));
281 Ok(option)
282 }
283
284 make_bool_getter!(Disabled, "disabled");
286
287 make_bool_setter!(SetDisabled, "disabled");
289
290 fn Text(&self) -> DOMString {
292 let mut content = DOMString::new();
293
294 let mut iterator = self.upcast::<Node>().traverse_preorder(ShadowIncluding::No);
295 while let Some(node) = iterator.peek() {
296 if let Some(element) = node.downcast::<Element>() {
297 let html_script = element.is::<HTMLScriptElement>();
298 let svg_script = *element.namespace() == ns!(svg) &&
299 element.local_name() == &local_name!("script");
300 if html_script || svg_script {
301 iterator.next_skipping_children();
302 continue;
303 }
304 }
305
306 if node.is::<Text>() {
307 let characterdata = node.downcast::<CharacterData>().unwrap();
308 content.push_str(&characterdata.Data().str());
309 }
310
311 iterator.next();
312 }
313
314 DOMString::from(str_join(split_html_space_chars(&content.str()), " "))
315 }
316
317 fn SetText(&self, cx: &mut JSContext, value: DOMString) {
319 self.upcast::<Node>()
320 .set_text_content_for_element(cx, Some(value))
321 }
322
323 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
325 let parent = self.upcast::<Node>().GetParentNode().and_then(|p| {
326 if p.is::<HTMLOptGroupElement>() {
327 p.upcast::<Node>().GetParentNode()
328 } else {
329 Some(p)
330 }
331 });
332
333 parent.and_then(|p| p.downcast::<HTMLSelectElement>().and_then(|s| s.GetForm()))
334 }
335
336 fn Value(&self) -> DOMString {
338 let element = self.upcast::<Element>();
339 let attr = &local_name!("value");
340 if element.has_attribute(attr) {
341 element.get_string_attribute(attr)
342 } else {
343 self.Text()
344 }
345 }
346
347 make_setter!(SetValue, "value");
349
350 fn Label(&self) -> DOMString {
352 let element = self.upcast::<Element>();
353 let attr = &local_name!("label");
354 if element.has_attribute(attr) {
355 element.get_string_attribute(attr)
356 } else {
357 self.Text()
358 }
359 }
360
361 make_setter!(SetLabel, "label");
363
364 make_bool_getter!(DefaultSelected, "selected");
366
367 make_bool_setter!(SetDefaultSelected, "selected");
369
370 fn Selected(&self) -> bool {
372 self.selectedness.get()
373 }
374
375 fn SetSelected(&self, cx: &mut JSContext, selected: bool) {
377 self.dirtiness.set(true);
378 self.set_selectedness(selected);
379 self.pick_if_selected_and_reset();
380 self.update_select_validity(CanGc::from_cx(cx));
381 }
382
383 fn Index(&self) -> i32 {
385 self.index()
386 }
387}
388
389impl VirtualMethods for HTMLOptionElement {
390 fn super_type(&self) -> Option<&dyn VirtualMethods> {
391 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
392 }
393
394 fn attribute_mutated(
395 &self,
396 cx: &mut js::context::JSContext,
397 attr: AttrRef<'_>,
398 mutation: AttributeMutation,
399 ) {
400 self.super_type()
401 .unwrap()
402 .attribute_mutated(cx, attr, mutation);
403 match *attr.local_name() {
404 local_name!("disabled") => {
405 let el = self.upcast::<Element>();
406 match mutation {
407 AttributeMutation::Set(..) => {
408 el.set_disabled_state(true);
409 el.set_enabled_state(false);
410 },
411 AttributeMutation::Removed => {
412 el.set_disabled_state(false);
413 el.set_enabled_state(true);
414 el.check_parent_disabled_state_for_option();
415 },
416 }
417 self.update_select_validity(CanGc::from_cx(cx));
418 },
419 local_name!("selected") => {
420 let mut selectedness_changed = false;
421 match mutation {
422 AttributeMutation::Set(..) => {
423 if !self.dirtiness.get() && !self.selectedness.get() {
425 self.set_selectedness(true);
426 selectedness_changed = true;
427 }
428 },
429 AttributeMutation::Removed => {
430 if !self.dirtiness.get() && self.selectedness.get() {
432 self.set_selectedness(false);
433 selectedness_changed = true;
434 }
435 },
436 }
437
438 if selectedness_changed {
439 self.pick_if_selected_and_reset();
440
441 if let Some(select_element) = self.owner_select_element() {
442 select_element.update_shadow_tree(cx);
443 }
444 }
445
446 self.update_select_validity(CanGc::from_cx(cx));
447 },
448 local_name!("label") => {
449 if let Some(select_element) = self.owner_select_element() {
452 select_element.update_shadow_tree(cx);
453 }
454 },
455 _ => {},
456 }
457 }
458
459 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
460 if let Some(s) = self.super_type() {
461 s.bind_to_tree(cx, context);
462 }
463
464 self.upcast::<Element>()
465 .check_parent_disabled_state_for_option();
466
467 self.pick_if_selected_and_reset();
468 self.update_select_validity(CanGc::from_cx(cx));
469 }
470
471 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
472 self.super_type().unwrap().unbind_from_tree(cx, context);
473
474 if let Some(select) = context
475 .parent
476 .inclusive_ancestors(ShadowIncluding::No)
477 .find_map(DomRoot::downcast::<HTMLSelectElement>)
478 {
479 select
480 .validity_state(CanGc::from_cx(cx))
481 .perform_validation_and_update(ValidationFlags::all(), CanGc::from_cx(cx));
482 select.ask_for_reset();
483 }
484
485 let node = self.upcast::<Node>();
486 let el = self.upcast::<Element>();
487 if node.GetParentNode().is_some() {
488 el.check_parent_disabled_state_for_option();
489 } else {
490 el.check_disabled_attribute();
491 }
492 }
493
494 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
495 if let Some(super_type) = self.super_type() {
496 super_type.children_changed(cx, mutation);
497 }
498
499 if !self
502 .upcast::<Element>()
503 .has_attribute(&local_name!("label")) &&
504 let Some(owner_select) = self.owner_select_element() &&
505 owner_select
506 .selected_option()
507 .is_some_and(|selected_option| self == &*selected_option)
508 {
509 owner_select.update_shadow_tree(cx);
510 }
511 }
512
513 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
515 if let Some(super_type) = self.super_type() {
516 super_type.moving_steps(cx, context);
517 }
518
519 let element = self.upcast::<Element>();
522 if let Some(old_parent) = context.old_parent {
523 if let Some(select) = old_parent
524 .inclusive_ancestors(ShadowIncluding::No)
525 .find_map(DomRoot::downcast::<HTMLSelectElement>)
526 {
527 select
528 .validity_state(CanGc::from_cx(cx))
529 .perform_validation_and_update(ValidationFlags::all(), CanGc::from_cx(cx));
530 select.ask_for_reset();
531 }
532
533 if self.upcast::<Node>().GetParentNode().is_some() {
534 element.check_parent_disabled_state_for_option();
535 } else {
536 element.check_disabled_attribute();
537 }
538 }
539
540 element.check_parent_disabled_state_for_option();
541
542 self.pick_if_selected_and_reset();
543 self.update_select_validity(CanGc::from_cx(cx));
544 }
545}