/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use script_bindings::codegen::GenericBindings::HTMLButtonElementBinding::HTMLButtonElementMethods; use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods; use script_bindings::codegen::GenericBindings::HTMLInputElementBinding::HTMLInputElementMethods; use script_bindings::codegen::GenericBindings::HTMLOptionElementBinding::HTMLOptionElementMethods; use script_bindings::codegen::GenericBindings::HTMLSelectElementBinding::HTMLSelectElementMethods; use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods; use script_bindings::inheritance::Castable; use script_bindings::root::DomRoot; use script_bindings::script_runtime::CanGc; use crate::dom::node::{Node, NodeTraits, ShadowIncluding}; use crate::dom::types::{ HTMLAnchorElement, HTMLButtonElement, HTMLElement, HTMLFieldSetElement, HTMLInputElement, HTMLLabelElement, HTMLLegendElement, HTMLOptionElement, }; /// This is an implementation of . Note /// that there are various things called "commands" on the web platform, but this is the one that /// is mainly associated with access keys. pub(crate) enum InteractiveElementCommand { Anchor(DomRoot), Button(DomRoot), Input(DomRoot), Option(DomRoot), HTMLElement(DomRoot), } impl TryFrom<&HTMLLegendElement> for InteractiveElementCommand { type Error = (); /// From /// A legend element defines a command if all of the following are true: /// - It has an assigned access key. /// - It is a child of a fieldset element. /// - Its parent has a descendant that defines a command that is neither a label element nor /// a legend element. This element, if it exists, is the legend element's accesskey /// delegatee. fn try_from(legend_element: &HTMLLegendElement) -> Result { if !legend_element .owner_document() .event_handler() .has_assigned_access_key(legend_element.upcast()) { return Err(()); } let node = legend_element.upcast::(); let Some(parent) = node.GetParentElement() else { return Err(()); }; if !parent.is::() { return Err(()); } for node in parent .upcast::() .traverse_preorder(ShadowIncluding::No) { if node.is::() || node.is::() { continue; } let Some(html_element) = node.downcast::() else { continue; }; if let Ok(command) = Self::try_from(html_element) { return Ok(command); } } Err(()) } } impl TryFrom<&HTMLElement> for InteractiveElementCommand { type Error = (); fn try_from(html_element: &HTMLElement) -> Result { if let Some(anchor_element) = html_element.downcast::() { return Ok(Self::Anchor(DomRoot::from_ref(anchor_element))); } if let Some(button_element) = html_element.downcast::() { return Ok(Self::Button(DomRoot::from_ref(button_element))); } if let Some(input_element) = html_element.downcast::() { return Ok(Self::Input(DomRoot::from_ref(input_element))); } if let Some(option_element) = html_element.downcast::() { return Ok(Self::Option(DomRoot::from_ref(option_element))); } if let Some(legend_element) = html_element.downcast::() { return Self::try_from(legend_element); } if html_element .owner_document() .event_handler() .has_assigned_access_key(html_element) { return Ok(Self::HTMLElement(DomRoot::from_ref(html_element))); } Err(()) } } impl InteractiveElementCommand { pub(crate) fn disabled(&self) -> bool { match self { // // > The Disabled State facet of the command is true if the element or one of its // > ancestors is inert, and false otherwise. // TODO: We do not support `inert` yet. InteractiveElementCommand::Anchor(..) => false, // // > The Disabled State of the command is true if the element or one of its ancestors // > is inert, or if the element's disabled state is set, and false otherwise. // TODO: We do not support `inert` yet. InteractiveElementCommand::Button(button) => button.Disabled(), // // > The Disabled State of the command is true if the element or one of its ancestors is // > inert, or if the element's disabled state is set, and false otherwise. // TODO: We do not support `inert` yet. InteractiveElementCommand::Input(input) => input.Disabled(), // // > The Disabled State of the command is true if the element is disabled, or if its // > nearest ancestor select element is disabled, or if it or one of its ancestors is // > inert, and false otherwise. // TODO: We do not support `inert` yet. InteractiveElementCommand::Option(option) => { option.Disabled() || option .nearest_ancestor_select() .is_some_and(|select| select.Disabled()) }, // // > The Disabled State of the command is true if the element or one of its ancestors is // > inert, and false otherwise. // TODO: We do not support `inert` yet. InteractiveElementCommand::HTMLElement(..) => false, } } pub(crate) fn hidden(&self) -> bool { let html_element: &HTMLElement = match self { InteractiveElementCommand::Anchor(anchor_element) => anchor_element.upcast(), InteractiveElementCommand::Button(button_element) => button_element.upcast(), InteractiveElementCommand::Input(input_element) => input_element.upcast(), InteractiveElementCommand::Option(option_element) => option_element.upcast(), InteractiveElementCommand::HTMLElement(html_element) => html_element, }; html_element.Hidden() } pub(crate) fn perform_action(&self, can_gc: CanGc) { match self { // // > The Action of the command is to fire a click event at the element. // // > Firing a click event at target means firing a synthetic pointer event named click at target. InteractiveElementCommand::Anchor(anchor_element) => anchor_element .upcast::() .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc), // // > The Label, Access Key, Hidden State, and Action facets of the command are // > determined as for a elements (see the previous section). InteractiveElementCommand::Button(button_element) => button_element .upcast::() .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc), // // > The Action of the command is to fire a click event at the element. InteractiveElementCommand::Input(input_element) => input_element .upcast::() .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc), // // > If the option's nearest ancestor select element has a multiple attribute, the // > Action of the command is to toggle the option element. Otherwise, the Action is to // > pick the option element. // Note: setSelected takes care of whether or not the owner has the `multiple` attribute. InteractiveElementCommand::Option(option_element) => { option_element.SetSelected(true, can_gc) }, // > The Action of the command is to run the following steps: // > 1. Run the focusing steps for the element. // > 2. Fire a click event at the element. InteractiveElementCommand::HTMLElement(html_element) => { let node: &Node = html_element.upcast(); node.run_the_focusing_steps(None, can_gc); node.fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc); }, } } }