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