script/dom/html/interactive_element_command.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use script_bindings::codegen::GenericBindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
6use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
7use script_bindings::codegen::GenericBindings::HTMLInputElementBinding::HTMLInputElementMethods;
8use script_bindings::codegen::GenericBindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
9use script_bindings::codegen::GenericBindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
10use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
11use script_bindings::inheritance::Castable;
12use script_bindings::root::DomRoot;
13use script_bindings::script_runtime::CanGc;
14
15use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
16use crate::dom::types::{
17 HTMLAnchorElement, HTMLButtonElement, HTMLElement, HTMLFieldSetElement, HTMLInputElement,
18 HTMLLabelElement, HTMLLegendElement, HTMLOptionElement,
19};
20
21/// This is an implementation of <https://html.spec.whatwg.org/multipage/#concept-command>. Note
22/// that there are various things called "commands" on the web platform, but this is the one that
23/// is mainly associated with access keys.
24pub(crate) enum InteractiveElementCommand {
25 Anchor(DomRoot<HTMLAnchorElement>),
26 Button(DomRoot<HTMLButtonElement>),
27 Input(DomRoot<HTMLInputElement>),
28 Option(DomRoot<HTMLOptionElement>),
29 HTMLElement(DomRoot<HTMLElement>),
30}
31
32impl TryFrom<&HTMLLegendElement> for InteractiveElementCommand {
33 type Error = ();
34
35 /// From <https://html.spec.whatwg.org/multipage/#using-the-accesskey-attribute-on-a-legend-element-to-define-a-command>
36 /// A legend element defines a command if all of the following are true:
37 /// - It has an assigned access key.
38 /// - It is a child of a fieldset element.
39 /// - Its parent has a descendant that defines a command that is neither a label element nor
40 /// a legend element. This element, if it exists, is the legend element's accesskey
41 /// delegatee.
42 fn try_from(legend_element: &HTMLLegendElement) -> Result<Self, Self::Error> {
43 if !legend_element
44 .owner_document()
45 .event_handler()
46 .has_assigned_access_key(legend_element.upcast())
47 {
48 return Err(());
49 }
50
51 let node = legend_element.upcast::<Node>();
52 let Some(parent) = node.GetParentElement() else {
53 return Err(());
54 };
55 if !parent.is::<HTMLFieldSetElement>() {
56 return Err(());
57 }
58 for node in parent
59 .upcast::<Node>()
60 .traverse_preorder(ShadowIncluding::No)
61 {
62 if node.is::<HTMLLabelElement>() || node.is::<HTMLLegendElement>() {
63 continue;
64 }
65 let Some(html_element) = node.downcast::<HTMLElement>() else {
66 continue;
67 };
68 if let Ok(command) = Self::try_from(html_element) {
69 return Ok(command);
70 }
71 }
72
73 Err(())
74 }
75}
76
77impl TryFrom<&HTMLElement> for InteractiveElementCommand {
78 type Error = ();
79
80 fn try_from(html_element: &HTMLElement) -> Result<Self, Self::Error> {
81 if let Some(anchor_element) = html_element.downcast::<HTMLAnchorElement>() {
82 return Ok(Self::Anchor(DomRoot::from_ref(anchor_element)));
83 }
84 if let Some(button_element) = html_element.downcast::<HTMLButtonElement>() {
85 return Ok(Self::Button(DomRoot::from_ref(button_element)));
86 }
87 if let Some(input_element) = html_element.downcast::<HTMLInputElement>() {
88 return Ok(Self::Input(DomRoot::from_ref(input_element)));
89 }
90 if let Some(option_element) = html_element.downcast::<HTMLOptionElement>() {
91 return Ok(Self::Option(DomRoot::from_ref(option_element)));
92 }
93 if let Some(legend_element) = html_element.downcast::<HTMLLegendElement>() {
94 return Self::try_from(legend_element);
95 }
96 if html_element
97 .owner_document()
98 .event_handler()
99 .has_assigned_access_key(html_element)
100 {
101 return Ok(Self::HTMLElement(DomRoot::from_ref(html_element)));
102 }
103
104 Err(())
105 }
106}
107
108impl InteractiveElementCommand {
109 pub(crate) fn disabled(&self) -> bool {
110 match self {
111 // <https://html.spec.whatwg.org/multipage#using-the-a-element-to-define-a-command>
112 // > The Disabled State facet of the command is true if the element or one of its
113 // > ancestors is inert, and false otherwise.
114 // TODO: We do not support `inert` yet.
115 InteractiveElementCommand::Anchor(..) => false,
116 // <https://html.spec.whatwg.org/multipage/#using-the-button-element-to-define-a-command>
117 // > The Disabled State of the command is true if the element or one of its ancestors
118 // > is inert, or if the element's disabled state is set, and false otherwise.
119 // TODO: We do not support `inert` yet.
120 InteractiveElementCommand::Button(button) => button.Disabled(),
121 // <https://html.spec.whatwg.org/multipage/#using-the-input-element-to-define-a-command>
122 // > The Disabled State of the command is true if the element or one of its ancestors is
123 // > inert, or if the element's disabled state is set, and false otherwise.
124 // TODO: We do not support `inert` yet.
125 InteractiveElementCommand::Input(input) => input.Disabled(),
126 // <https://html.spec.whatwg.org/multipage/#using-the-option-element-to-define-a-command>
127 // > The Disabled State of the command is true if the element is disabled, or if its
128 // > nearest ancestor select element is disabled, or if it or one of its ancestors is
129 // > inert, and false otherwise.
130 // TODO: We do not support `inert` yet.
131 InteractiveElementCommand::Option(option) => {
132 option.Disabled() ||
133 option
134 .nearest_ancestor_select()
135 .is_some_and(|select| select.Disabled())
136 },
137 // <https://html.spec.whatwg.org/multipage#using-the-accesskey-attribute-to-define-a-command-on-other-elements>
138 // > The Disabled State of the command is true if the element or one of its ancestors is
139 // > inert, and false otherwise.
140 // TODO: We do not support `inert` yet.
141 InteractiveElementCommand::HTMLElement(..) => false,
142 }
143 }
144
145 pub(crate) fn hidden(&self) -> bool {
146 let html_element: &HTMLElement = match self {
147 InteractiveElementCommand::Anchor(anchor_element) => anchor_element.upcast(),
148 InteractiveElementCommand::Button(button_element) => button_element.upcast(),
149 InteractiveElementCommand::Input(input_element) => input_element.upcast(),
150 InteractiveElementCommand::Option(option_element) => option_element.upcast(),
151 InteractiveElementCommand::HTMLElement(html_element) => html_element,
152 };
153 html_element.Hidden()
154 }
155
156 pub(crate) fn perform_action(&self, can_gc: CanGc) {
157 match self {
158 // <https://html.spec.whatwg.org/multipage#using-the-a-element-to-define-a-command>
159 // > The Action of the command is to fire a click event at the element.
160 // <https://html.spec.whatwg.org/multipage/#fire-a-click-event>
161 // > Firing a click event at target means firing a synthetic pointer event named click at target.
162 InteractiveElementCommand::Anchor(anchor_element) => anchor_element
163 .upcast::<Node>()
164 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc),
165 // <https://html.spec.whatwg.org/multipage/#using-the-button-element-to-define-a-command>
166 // > The Label, Access Key, Hidden State, and Action facets of the command are
167 // > determined as for a elements (see the previous section).
168 InteractiveElementCommand::Button(button_element) => button_element
169 .upcast::<Node>()
170 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc),
171 // <https://html.spec.whatwg.org/multipage/#using-the-input-element-to-define-a-command>
172 // > The Action of the command is to fire a click event at the element.
173 InteractiveElementCommand::Input(input_element) => input_element
174 .upcast::<Node>()
175 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc),
176 // <https://html.spec.whatwg.org/multipage/#using-the-option-element-to-define-a-command>
177 // > If the option's nearest ancestor select element has a multiple attribute, the
178 // > Action of the command is to toggle the option element. Otherwise, the Action is to
179 // > pick the option element.
180 // Note: setSelected takes care of whether or not the owner has the `multiple` attribute.
181 InteractiveElementCommand::Option(option_element) => {
182 option_element.SetSelected(true, can_gc)
183 },
184 // > The Action of the command is to run the following steps:
185 // > 1. Run the focusing steps for the element.
186 // > 2. Fire a click event at the element.
187 InteractiveElementCommand::HTMLElement(html_element) => {
188 let node: &Node = html_element.upcast();
189 node.run_the_focusing_steps(None, can_gc);
190 node.fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
191 },
192 }
193 }
194}