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::iterators::ShadowIncluding;
34use crate::dom::node::{
35 BindContext, ChildrenMutation, CloneChildrenFlag, MoveContext, Node, NodeTraits, 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;
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 cx: &mut js::context::JSContext,
75 local_name: LocalName,
76 prefix: Option<Prefix>,
77 document: &Document,
78 proto: Option<HandleObject>,
79 ) -> DomRoot<HTMLOptionElement> {
80 Node::reflect_node_with_proto(
81 cx,
82 Box::new(HTMLOptionElement::new_inherited(
83 local_name, prefix, document,
84 )),
85 document,
86 proto,
87 )
88 }
89
90 pub(crate) fn set_selectedness(&self, selected: bool) {
91 self.selectedness.set(selected);
92 self.upcast::<Node>().rev_version();
95 }
96
97 pub(crate) fn set_dirtiness(&self, dirtiness: bool) {
98 self.dirtiness.set(dirtiness);
99 }
100
101 fn pick_if_selected_and_reset(&self) {
102 if let Some(select) = self.owner_select_element() {
103 if self.Selected() {
104 select.pick_option(self);
105 }
106 select.ask_for_reset();
107 }
108 }
109
110 fn index(&self) -> i32 {
112 let Some(owner_select) = self.owner_select_element() else {
113 return 0;
114 };
115
116 let Some(position) = owner_select.list_of_options().position(|n| &*n == self) else {
117 warn!("HTMLOptionElement called index_in_select at a select that did not contain it");
119 return 0;
120 };
121
122 position.try_into().unwrap_or(0)
123 }
124
125 fn owner_select_element(&self) -> Option<DomRoot<HTMLSelectElement>> {
126 let parent = self.upcast::<Node>().GetParentNode()?;
127
128 if parent.is::<HTMLOptGroupElement>() {
129 DomRoot::downcast::<HTMLSelectElement>(parent.GetParentNode()?)
130 } else {
131 DomRoot::downcast::<HTMLSelectElement>(parent)
132 }
133 }
134
135 fn update_select_validity(&self, cx: &mut JSContext) {
136 if let Some(select) = self.owner_select_element() {
137 select
138 .validity_state(cx)
139 .perform_validation_and_update(cx, ValidationFlags::all());
140 }
141 }
142
143 pub(crate) fn displayed_label(&self) -> DOMString {
147 let label = self
150 .upcast::<Element>()
151 .get_string_attribute(&local_name!("label"));
152
153 if label.is_empty() {
154 return self.Text();
155 }
156
157 label
158 }
159
160 pub(crate) fn nearest_ancestor_select(&self) -> Option<DomRoot<HTMLSelectElement>> {
162 let mut did_see_ancestor_optgroup = false;
165
166 for ancestor in self
168 .upcast::<Node>()
169 .ancestors()
170 .filter_map(DomRoot::downcast::<Element>)
171 {
172 if matches!(
174 ancestor.local_name(),
175 &local_name!("datalist") | &local_name!("hr") | &local_name!("option")
176 ) {
177 return None;
178 }
179
180 if ancestor.local_name() == &local_name!("optgroup") {
182 if did_see_ancestor_optgroup {
184 return None;
185 }
186
187 did_see_ancestor_optgroup = true;
189 }
190
191 if let Some(select) = DomRoot::downcast::<HTMLSelectElement>(ancestor) {
193 return Some(select);
194 }
195 }
196
197 None
199 }
200
201 pub(crate) fn maybe_clone_an_option_into_selectedcontent(&self, cx: &mut JSContext) {
203 let select = self.nearest_ancestor_select();
205
206 if self.selectedness.get() &&
212 let Some(selectedcontent) =
213 select.and_then(|select| select.get_enabled_selectedcontent())
214 {
215 self.clone_an_option_into_selectedcontent(cx, &selectedcontent);
216 }
217 }
218
219 fn clone_an_option_into_selectedcontent(&self, cx: &mut JSContext, selectedcontent: &Element) {
221 let document_fragment = DocumentFragment::new(cx, &self.owner_document());
223
224 for child in self.upcast::<Node>().children() {
226 let child_clone = Node::clone(cx, &child, None, CloneChildrenFlag::CloneChildren, None);
228
229 let _ = document_fragment
231 .upcast::<Node>()
232 .AppendChild(cx, &child_clone);
233 }
234
235 Node::replace_all(
237 cx,
238 Some(document_fragment.upcast()),
239 selectedcontent.upcast(),
240 );
241 }
242}
243
244impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
245 fn Option(
247 cx: &mut JSContext,
248 window: &Window,
249 proto: Option<HandleObject>,
250 text: DOMString,
251 value: Option<DOMString>,
252 default_selected: bool,
253 selected: bool,
254 ) -> Fallible<DomRoot<HTMLOptionElement>> {
255 let element = Element::create(
256 cx,
257 QualName::new(None, ns!(html), local_name!("option")),
258 None,
259 &window.Document(),
260 ElementCreator::ScriptCreated,
261 CustomElementCreationMode::Synchronous,
262 proto,
263 );
264
265 let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap();
266
267 if !text.is_empty() {
268 option
269 .upcast::<Node>()
270 .set_text_content_for_element(cx, Some(text))
271 }
272
273 if let Some(val) = value {
274 option.SetValue(cx, val)
275 }
276
277 option.SetDefaultSelected(cx, default_selected);
278 option.set_selectedness(selected);
279 option.update_select_validity(cx);
280 Ok(option)
281 }
282
283 make_bool_getter!(Disabled, "disabled");
285
286 make_bool_setter!(SetDisabled, "disabled");
288
289 fn Text(&self) -> DOMString {
291 let mut content = DOMString::new();
292
293 let mut iterator = self.upcast::<Node>().traverse_preorder(ShadowIncluding::No);
294 while let Some(node) = iterator.peek() {
295 if let Some(element) = node.downcast::<Element>() {
296 let html_script = element.is::<HTMLScriptElement>();
297 let svg_script = *element.namespace() == ns!(svg) &&
298 element.local_name() == &local_name!("script");
299 if html_script || svg_script {
300 iterator.next_skipping_children();
301 continue;
302 }
303 }
304
305 if node.is::<Text>() {
306 let characterdata = node.downcast::<CharacterData>().unwrap();
307 content.push_str(&characterdata.Data().str());
308 }
309
310 iterator.next();
311 }
312
313 DOMString::from(str_join(split_html_space_chars(&content.str()), " "))
314 }
315
316 fn SetText(&self, cx: &mut JSContext, value: DOMString) {
318 self.upcast::<Node>()
319 .set_text_content_for_element(cx, Some(value))
320 }
321
322 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
324 let parent = self.upcast::<Node>().GetParentNode().and_then(|p| {
325 if p.is::<HTMLOptGroupElement>() {
326 p.upcast::<Node>().GetParentNode()
327 } else {
328 Some(p)
329 }
330 });
331
332 parent.and_then(|p| p.downcast::<HTMLSelectElement>().and_then(|s| s.GetForm()))
333 }
334
335 fn Value(&self) -> DOMString {
337 let element = self.upcast::<Element>();
338 let attr = &local_name!("value");
339 if element.has_attribute(attr) {
340 element.get_string_attribute(attr)
341 } else {
342 self.Text()
343 }
344 }
345
346 make_setter!(SetValue, "value");
348
349 fn Label(&self) -> DOMString {
351 let element = self.upcast::<Element>();
352 let attr = &local_name!("label");
353 if element.has_attribute(attr) {
354 element.get_string_attribute(attr)
355 } else {
356 self.Text()
357 }
358 }
359
360 make_setter!(SetLabel, "label");
362
363 make_bool_getter!(DefaultSelected, "selected");
365
366 make_bool_setter!(SetDefaultSelected, "selected");
368
369 fn Selected(&self) -> bool {
371 self.selectedness.get()
372 }
373
374 fn SetSelected(&self, cx: &mut JSContext, selected: bool) {
376 self.dirtiness.set(true);
377 self.set_selectedness(selected);
378 self.pick_if_selected_and_reset();
379 self.update_select_validity(cx);
380 }
381
382 fn Index(&self) -> i32 {
384 self.index()
385 }
386}
387
388impl VirtualMethods for HTMLOptionElement {
389 fn super_type(&self) -> Option<&dyn VirtualMethods> {
390 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
391 }
392
393 fn attribute_mutated(
394 &self,
395 cx: &mut js::context::JSContext,
396 attr: AttrRef<'_>,
397 mutation: AttributeMutation,
398 ) {
399 self.super_type()
400 .unwrap()
401 .attribute_mutated(cx, attr, mutation);
402 match *attr.local_name() {
403 local_name!("disabled") => {
404 let el = self.upcast::<Element>();
405 match mutation {
406 AttributeMutation::Set(..) => {
407 el.set_disabled_state(true);
408 el.set_enabled_state(false);
409 },
410 AttributeMutation::Removed => {
411 el.set_disabled_state(false);
412 el.set_enabled_state(true);
413 el.check_parent_disabled_state_for_option();
414 },
415 }
416 self.update_select_validity(cx);
417 },
418 local_name!("selected") => {
419 let mut selectedness_changed = false;
420 match mutation {
421 AttributeMutation::Set(..) => {
422 if !self.dirtiness.get() && !self.selectedness.get() {
424 self.set_selectedness(true);
425 selectedness_changed = true;
426 }
427 },
428 AttributeMutation::Removed => {
429 if !self.dirtiness.get() && self.selectedness.get() {
431 self.set_selectedness(false);
432 selectedness_changed = true;
433 }
434 },
435 }
436
437 if selectedness_changed {
438 self.pick_if_selected_and_reset();
439
440 if let Some(select_element) = self.owner_select_element() {
441 select_element.update_shadow_tree(cx);
442 }
443 }
444
445 self.update_select_validity(cx);
446 },
447 local_name!("label") => {
448 if let Some(select_element) = self.owner_select_element() {
451 select_element.update_shadow_tree(cx);
452 }
453 },
454 _ => {},
455 }
456 }
457
458 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
459 if let Some(s) = self.super_type() {
460 s.bind_to_tree(cx, context);
461 }
462
463 self.upcast::<Element>()
464 .check_parent_disabled_state_for_option();
465
466 self.pick_if_selected_and_reset();
467 self.update_select_validity(cx);
468 }
469
470 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
471 self.super_type().unwrap().unbind_from_tree(cx, context);
472
473 if let Some(select) = context
474 .parent
475 .inclusive_ancestors(ShadowIncluding::No)
476 .find_map(DomRoot::downcast::<HTMLSelectElement>)
477 {
478 select
479 .validity_state(cx)
480 .perform_validation_and_update(cx, ValidationFlags::all());
481 select.ask_for_reset();
482 }
483
484 let node = self.upcast::<Node>();
485 let el = self.upcast::<Element>();
486 if node.GetParentNode().is_some() {
487 el.check_parent_disabled_state_for_option();
488 } else {
489 el.check_disabled_attribute();
490 }
491 }
492
493 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
494 if let Some(super_type) = self.super_type() {
495 super_type.children_changed(cx, mutation);
496 }
497
498 if !self
501 .upcast::<Element>()
502 .has_attribute(&local_name!("label")) &&
503 let Some(owner_select) = self.owner_select_element() &&
504 owner_select
505 .selected_option()
506 .is_some_and(|selected_option| self == &*selected_option)
507 {
508 owner_select.update_shadow_tree(cx);
509 }
510 }
511
512 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
514 if let Some(super_type) = self.super_type() {
515 super_type.moving_steps(cx, context);
516 }
517
518 let element = self.upcast::<Element>();
521 if let Some(old_parent) = context.old_parent {
522 if let Some(select) = old_parent
523 .inclusive_ancestors(ShadowIncluding::No)
524 .find_map(DomRoot::downcast::<HTMLSelectElement>)
525 {
526 select
527 .validity_state(cx)
528 .perform_validation_and_update(cx, ValidationFlags::all());
529 select.ask_for_reset();
530 }
531
532 if self.upcast::<Node>().GetParentNode().is_some() {
533 element.check_parent_disabled_state_for_option();
534 } else {
535 element.check_disabled_attribute();
536 }
537 }
538
539 element.check_parent_disabled_state_for_option();
540
541 self.pick_if_selected_and_reset();
542 self.update_select_validity(cx);
543 }
544}