script/dom/document/
interactive_element_command.rs1use js::context::JSContext;
6use script_bindings::codegen::GenericBindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
7use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
8use script_bindings::codegen::GenericBindings::HTMLInputElementBinding::HTMLInputElementMethods;
9use script_bindings::codegen::GenericBindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
10use script_bindings::codegen::GenericBindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
11use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
12use script_bindings::inheritance::Castable;
13use script_bindings::root::DomRoot;
14
15use crate::dom::iterators::ShadowIncluding;
16use crate::dom::node::focus::FocusTrigger;
17use crate::dom::node::{Node, NodeTraits};
18use crate::dom::types::{
19 HTMLAnchorElement, HTMLButtonElement, HTMLElement, HTMLFieldSetElement, HTMLInputElement,
20 HTMLLabelElement, HTMLLegendElement, HTMLOptionElement,
21};
22
23pub(crate) enum InteractiveElementCommand {
27 Anchor(DomRoot<HTMLAnchorElement>),
28 Button(DomRoot<HTMLButtonElement>),
29 Input(DomRoot<HTMLInputElement>),
30 Option(DomRoot<HTMLOptionElement>),
31 HTMLElement(DomRoot<HTMLElement>),
32}
33
34impl TryFrom<&HTMLLegendElement> for InteractiveElementCommand {
35 type Error = ();
36
37 fn try_from(legend_element: &HTMLLegendElement) -> Result<Self, Self::Error> {
45 if !legend_element
46 .owner_document()
47 .event_handler()
48 .has_assigned_access_key(legend_element.upcast())
49 {
50 return Err(());
51 }
52
53 let node = legend_element.upcast::<Node>();
54 let Some(parent) = node.GetParentElement() else {
55 return Err(());
56 };
57 if !parent.is::<HTMLFieldSetElement>() {
58 return Err(());
59 }
60 for node in parent
61 .upcast::<Node>()
62 .traverse_preorder(ShadowIncluding::No)
63 {
64 if node.is::<HTMLLabelElement>() || node.is::<HTMLLegendElement>() {
65 continue;
66 }
67 let Some(html_element) = node.downcast::<HTMLElement>() else {
68 continue;
69 };
70 if let Ok(command) = Self::try_from(html_element) {
71 return Ok(command);
72 }
73 }
74
75 Err(())
76 }
77}
78
79impl TryFrom<&HTMLElement> for InteractiveElementCommand {
80 type Error = ();
81
82 fn try_from(html_element: &HTMLElement) -> Result<Self, Self::Error> {
83 if let Some(anchor_element) = html_element.downcast::<HTMLAnchorElement>() {
84 return Ok(Self::Anchor(DomRoot::from_ref(anchor_element)));
85 }
86 if let Some(button_element) = html_element.downcast::<HTMLButtonElement>() {
87 return Ok(Self::Button(DomRoot::from_ref(button_element)));
88 }
89 if let Some(input_element) = html_element.downcast::<HTMLInputElement>() {
90 return Ok(Self::Input(DomRoot::from_ref(input_element)));
91 }
92 if let Some(option_element) = html_element.downcast::<HTMLOptionElement>() {
93 return Ok(Self::Option(DomRoot::from_ref(option_element)));
94 }
95 if let Some(legend_element) = html_element.downcast::<HTMLLegendElement>() {
96 return Self::try_from(legend_element);
97 }
98 if html_element
99 .owner_document()
100 .event_handler()
101 .has_assigned_access_key(html_element)
102 {
103 return Ok(Self::HTMLElement(DomRoot::from_ref(html_element)));
104 }
105
106 Err(())
107 }
108}
109
110impl InteractiveElementCommand {
111 fn is_node_or_ancestor_inert(node: &Node) -> bool {
112 node.inclusive_ancestors(ShadowIncluding::No)
113 .any(|node| node.is_inert())
114 }
115
116 pub(crate) fn disabled(&self) -> bool {
117 match self {
118 InteractiveElementCommand::Anchor(anchor) => {
122 Self::is_node_or_ancestor_inert(anchor.upcast::<Node>())
123 },
124 InteractiveElementCommand::Button(button) => {
128 Self::is_node_or_ancestor_inert(button.upcast::<Node>()) || button.Disabled()
129 },
130 InteractiveElementCommand::Input(input) => {
134 Self::is_node_or_ancestor_inert(input.upcast::<Node>()) || input.Disabled()
135 },
136 InteractiveElementCommand::Option(option) => {
141 option.Disabled() ||
142 option
143 .nearest_ancestor_select()
144 .is_some_and(|select| select.Disabled()) ||
145 Self::is_node_or_ancestor_inert(option.upcast::<Node>())
146 },
147 InteractiveElementCommand::HTMLElement(element) => {
151 Self::is_node_or_ancestor_inert(element.upcast::<Node>())
152 },
153 }
154 }
155
156 pub(crate) fn hidden(&self) -> bool {
157 let html_element: &HTMLElement = match self {
158 InteractiveElementCommand::Anchor(anchor_element) => anchor_element.upcast(),
159 InteractiveElementCommand::Button(button_element) => button_element.upcast(),
160 InteractiveElementCommand::Input(input_element) => input_element.upcast(),
161 InteractiveElementCommand::Option(option_element) => option_element.upcast(),
162 InteractiveElementCommand::HTMLElement(html_element) => html_element,
163 };
164 html_element.Hidden()
165 }
166
167 pub(crate) fn perform_action(&self, cx: &mut JSContext) {
168 match self {
169 InteractiveElementCommand::Anchor(anchor_element) => anchor_element
174 .upcast::<Node>()
175 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click")),
176 InteractiveElementCommand::Button(button_element) => button_element
180 .upcast::<Node>()
181 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click")),
182 InteractiveElementCommand::Input(input_element) => input_element
185 .upcast::<Node>()
186 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click")),
187 InteractiveElementCommand::Option(option_element) => {
193 option_element.SetSelected(cx, true)
194 },
195 InteractiveElementCommand::HTMLElement(html_element) => {
199 let node: &Node = html_element.upcast();
200 node.run_the_focusing_steps(cx, None, FocusTrigger::Other);
201 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
202 },
203 }
204 }
205}