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::{BindContext, ChildrenMutation, Node, ShadowIncluding, UnbindContext};
33use crate::dom::text::Text;
34use crate::dom::validation::Validatable;
35use crate::dom::validitystate::ValidationFlags;
36use crate::dom::virtualmethods::VirtualMethods;
37use crate::dom::window::Window;
38use crate::script_runtime::CanGc;
39
40#[dom_struct]
41pub(crate) struct HTMLOptionElement {
42 htmlelement: HTMLElement,
43
44 selectedness: Cell<bool>,
46
47 dirtiness: Cell<bool>,
49}
50
51impl HTMLOptionElement {
52 fn new_inherited(
53 local_name: LocalName,
54 prefix: Option<Prefix>,
55 document: &Document,
56 ) -> HTMLOptionElement {
57 HTMLOptionElement {
58 htmlelement: HTMLElement::new_inherited_with_state(
59 ElementState::ENABLED,
60 local_name,
61 prefix,
62 document,
63 ),
64 selectedness: Cell::new(false),
65 dirtiness: Cell::new(false),
66 }
67 }
68
69 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
70 pub(crate) fn new(
71 local_name: LocalName,
72 prefix: Option<Prefix>,
73 document: &Document,
74 proto: Option<HandleObject>,
75 can_gc: CanGc,
76 ) -> DomRoot<HTMLOptionElement> {
77 Node::reflect_node_with_proto(
78 Box::new(HTMLOptionElement::new_inherited(
79 local_name, prefix, document,
80 )),
81 document,
82 proto,
83 can_gc,
84 )
85 }
86
87 pub(crate) fn set_selectedness(&self, selected: bool) {
88 self.selectedness.set(selected);
89 }
90
91 pub(crate) fn set_dirtiness(&self, dirtiness: bool) {
92 self.dirtiness.set(dirtiness);
93 }
94
95 fn pick_if_selected_and_reset(&self) {
96 if let Some(select) = self.owner_select_element() {
97 if self.Selected() {
98 select.pick_option(self);
99 }
100 select.ask_for_reset();
101 }
102 }
103
104 fn index(&self) -> i32 {
106 let Some(owner_select) = self.owner_select_element() else {
107 return 0;
108 };
109
110 let Some(position) = owner_select.list_of_options().position(|n| &*n == self) else {
111 warn!("HTMLOptionElement called index_in_select at a select that did not contain it");
113 return 0;
114 };
115
116 position.try_into().unwrap_or(0)
117 }
118
119 fn owner_select_element(&self) -> Option<DomRoot<HTMLSelectElement>> {
120 let parent = self.upcast::<Node>().GetParentNode()?;
121
122 if parent.is::<HTMLOptGroupElement>() {
123 DomRoot::downcast::<HTMLSelectElement>(parent.GetParentNode()?)
124 } else {
125 DomRoot::downcast::<HTMLSelectElement>(parent)
126 }
127 }
128
129 fn update_select_validity(&self, can_gc: CanGc) {
130 if let Some(select) = self.owner_select_element() {
131 select
132 .validity_state()
133 .perform_validation_and_update(ValidationFlags::all(), can_gc);
134 }
135 }
136
137 pub(crate) fn displayed_label(&self) -> DOMString {
141 let label = self
144 .upcast::<Element>()
145 .get_string_attribute(&local_name!("label"));
146
147 if label.is_empty() {
148 return self.Text();
149 }
150
151 label
152 }
153}
154
155impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
156 fn Option(
158 window: &Window,
159 proto: Option<HandleObject>,
160 can_gc: CanGc,
161 text: DOMString,
162 value: Option<DOMString>,
163 default_selected: bool,
164 selected: bool,
165 ) -> Fallible<DomRoot<HTMLOptionElement>> {
166 let element = Element::create(
167 QualName::new(None, ns!(html), local_name!("option")),
168 None,
169 &window.Document(),
170 ElementCreator::ScriptCreated,
171 CustomElementCreationMode::Synchronous,
172 proto,
173 can_gc,
174 );
175
176 let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap();
177
178 if !text.is_empty() {
179 option
180 .upcast::<Node>()
181 .set_text_content_for_element(Some(text), can_gc)
182 }
183
184 if let Some(val) = value {
185 option.SetValue(val)
186 }
187
188 option.SetDefaultSelected(default_selected);
189 option.set_selectedness(selected);
190 option.update_select_validity(can_gc);
191 Ok(option)
192 }
193
194 make_bool_getter!(Disabled, "disabled");
196
197 make_bool_setter!(SetDisabled, "disabled");
199
200 fn Text(&self) -> DOMString {
202 let mut content = DOMString::new();
203
204 let mut iterator = self.upcast::<Node>().traverse_preorder(ShadowIncluding::No);
205 while let Some(node) = iterator.peek() {
206 if let Some(element) = node.downcast::<Element>() {
207 let html_script = element.is::<HTMLScriptElement>();
208 let svg_script = *element.namespace() == ns!(svg) &&
209 element.local_name() == &local_name!("script");
210 if html_script || svg_script {
211 iterator.next_skipping_children();
212 continue;
213 }
214 }
215
216 if node.is::<Text>() {
217 let characterdata = node.downcast::<CharacterData>().unwrap();
218 content.push_str(&characterdata.Data());
219 }
220
221 iterator.next();
222 }
223
224 DOMString::from(str_join(split_html_space_chars(&content), " "))
225 }
226
227 fn SetText(&self, value: DOMString, can_gc: CanGc) {
229 self.upcast::<Node>()
230 .set_text_content_for_element(Some(value), can_gc)
231 }
232
233 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
235 let parent = self.upcast::<Node>().GetParentNode().and_then(|p| {
236 if p.is::<HTMLOptGroupElement>() {
237 p.upcast::<Node>().GetParentNode()
238 } else {
239 Some(p)
240 }
241 });
242
243 parent.and_then(|p| p.downcast::<HTMLSelectElement>().and_then(|s| s.GetForm()))
244 }
245
246 fn Value(&self) -> DOMString {
248 let element = self.upcast::<Element>();
249 let attr = &local_name!("value");
250 if element.has_attribute(attr) {
251 element.get_string_attribute(attr)
252 } else {
253 self.Text()
254 }
255 }
256
257 make_setter!(SetValue, "value");
259
260 fn Label(&self) -> DOMString {
262 let element = self.upcast::<Element>();
263 let attr = &local_name!("label");
264 if element.has_attribute(attr) {
265 element.get_string_attribute(attr)
266 } else {
267 self.Text()
268 }
269 }
270
271 make_setter!(SetLabel, "label");
273
274 make_bool_getter!(DefaultSelected, "selected");
276
277 make_bool_setter!(SetDefaultSelected, "selected");
279
280 fn Selected(&self) -> bool {
282 self.selectedness.get()
283 }
284
285 fn SetSelected(&self, selected: bool) {
287 self.dirtiness.set(true);
288 self.selectedness.set(selected);
289 self.pick_if_selected_and_reset();
290 self.update_select_validity(CanGc::note());
291 }
292
293 fn Index(&self) -> i32 {
295 self.index()
296 }
297}
298
299impl VirtualMethods for HTMLOptionElement {
300 fn super_type(&self) -> Option<&dyn VirtualMethods> {
301 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
302 }
303
304 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
305 self.super_type()
306 .unwrap()
307 .attribute_mutated(attr, mutation, can_gc);
308 match *attr.local_name() {
309 local_name!("disabled") => {
310 let el = self.upcast::<Element>();
311 match mutation {
312 AttributeMutation::Set(_) => {
313 el.set_disabled_state(true);
314 el.set_enabled_state(false);
315 },
316 AttributeMutation::Removed => {
317 el.set_disabled_state(false);
318 el.set_enabled_state(true);
319 el.check_parent_disabled_state_for_option();
320 },
321 }
322 self.update_select_validity(can_gc);
323 },
324 local_name!("selected") => {
325 match mutation {
326 AttributeMutation::Set(_) => {
327 if !self.dirtiness.get() {
329 self.selectedness.set(true);
330 }
331 },
332 AttributeMutation::Removed => {
333 if !self.dirtiness.get() {
335 self.selectedness.set(false);
336 }
337 },
338 }
339 self.update_select_validity(can_gc);
340 },
341 local_name!("label") => {
342 if let Some(select_element) = self.owner_select_element() {
345 select_element.update_shadow_tree(CanGc::note());
346 }
347 },
348 _ => {},
349 }
350 }
351
352 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
353 if let Some(s) = self.super_type() {
354 s.bind_to_tree(context, can_gc);
355 }
356
357 self.upcast::<Element>()
358 .check_parent_disabled_state_for_option();
359
360 self.pick_if_selected_and_reset();
361 self.update_select_validity(can_gc);
362 }
363
364 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
365 self.super_type().unwrap().unbind_from_tree(context, can_gc);
366
367 if let Some(select) = context
368 .parent
369 .inclusive_ancestors(ShadowIncluding::No)
370 .filter_map(DomRoot::downcast::<HTMLSelectElement>)
371 .next()
372 {
373 select
374 .validity_state()
375 .perform_validation_and_update(ValidationFlags::all(), can_gc);
376 select.ask_for_reset();
377 }
378
379 let node = self.upcast::<Node>();
380 let el = self.upcast::<Element>();
381 if node.GetParentNode().is_some() {
382 el.check_parent_disabled_state_for_option();
383 } else {
384 el.check_disabled_attribute();
385 }
386 }
387
388 fn children_changed(&self, mutation: &ChildrenMutation) {
389 if let Some(super_type) = self.super_type() {
390 super_type.children_changed(mutation);
391 }
392
393 if !self
396 .upcast::<Element>()
397 .has_attribute(&local_name!("label"))
398 {
399 if let Some(owner_select) = self.owner_select_element() {
400 if owner_select
401 .selected_option()
402 .is_some_and(|selected_option| self == &*selected_option)
403 {
404 owner_select.update_shadow_tree(CanGc::note());
405 }
406 }
407 }
408 }
409}