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