| /* 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 std::default::Default; |
| |
| use dom_struct::dom_struct; |
| use html5ever::{LocalName, Prefix, local_name}; |
| use js::rust::HandleObject; |
| use stylo_dom::ElementState; |
| |
| use crate::dom::attr::Attr; |
| use crate::dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods; |
| use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; |
| use crate::dom::bindings::root::{DomRoot, MutNullableDom}; |
| use crate::dom::bindings::str::DOMString; |
| use crate::dom::customelementregistry::CallbackReaction; |
| use crate::dom::document::Document; |
| use crate::dom::element::{AttributeMutation, Element}; |
| use crate::dom::html::htmlcollection::HTMLCollection; |
| use crate::dom::html::htmlelement::HTMLElement; |
| use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement}; |
| use crate::dom::html::htmllegendelement::HTMLLegendElement; |
| use crate::dom::node::{Node, NodeTraits, ShadowIncluding}; |
| use crate::dom::validation::Validatable; |
| use crate::dom::validitystate::ValidityState; |
| use crate::dom::virtualmethods::VirtualMethods; |
| use crate::script_runtime::CanGc; |
| use crate::script_thread::ScriptThread; |
| |
| #[dom_struct] |
| pub(crate) struct HTMLFieldSetElement { |
| htmlelement: HTMLElement, |
| form_owner: MutNullableDom<HTMLFormElement>, |
| validity_state: MutNullableDom<ValidityState>, |
| } |
| |
| impl HTMLFieldSetElement { |
| fn new_inherited( |
| local_name: LocalName, |
| prefix: Option<Prefix>, |
| document: &Document, |
| ) -> HTMLFieldSetElement { |
| HTMLFieldSetElement { |
| htmlelement: HTMLElement::new_inherited_with_state( |
| ElementState::ENABLED | ElementState::VALID, |
| local_name, |
| prefix, |
| document, |
| ), |
| form_owner: Default::default(), |
| validity_state: Default::default(), |
| } |
| } |
| |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| pub(crate) fn new( |
| local_name: LocalName, |
| prefix: Option<Prefix>, |
| document: &Document, |
| proto: Option<HandleObject>, |
| can_gc: CanGc, |
| ) -> DomRoot<HTMLFieldSetElement> { |
| Node::reflect_node_with_proto( |
| Box::new(HTMLFieldSetElement::new_inherited( |
| local_name, prefix, document, |
| )), |
| document, |
| proto, |
| can_gc, |
| ) |
| } |
| |
| pub(crate) fn update_validity(&self, can_gc: CanGc) { |
| let has_invalid_child = self |
| .upcast::<Node>() |
| .traverse_preorder(ShadowIncluding::No) |
| .flat_map(DomRoot::downcast::<Element>) |
| .any(|element| element.is_invalid(false, can_gc)); |
| |
| self.upcast::<Element>() |
| .set_state(ElementState::VALID, !has_invalid_child); |
| self.upcast::<Element>() |
| .set_state(ElementState::INVALID, has_invalid_child); |
| } |
| } |
| |
| impl HTMLFieldSetElementMethods<crate::DomTypeHolder> for HTMLFieldSetElement { |
| // https://html.spec.whatwg.org/multipage/#dom-fieldset-elements |
| fn Elements(&self, can_gc: CanGc) -> DomRoot<HTMLCollection> { |
| HTMLCollection::new_with_filter_fn( |
| &self.owner_window(), |
| self.upcast(), |
| |element, _| { |
| element |
| .downcast::<HTMLElement>() |
| .is_some_and(HTMLElement::is_listed_element) |
| }, |
| can_gc, |
| ) |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled |
| make_bool_getter!(Disabled, "disabled"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled |
| make_bool_setter!(SetDisabled, "disabled"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-fe-name |
| make_atomic_setter!(SetName, "name"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-fe-name |
| make_getter!(Name, "name"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-fae-form |
| fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { |
| self.form_owner() |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate |
| fn WillValidate(&self) -> bool { |
| self.is_instance_validatable() |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-cva-validity |
| fn Validity(&self) -> DomRoot<ValidityState> { |
| self.validity_state() |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity |
| fn CheckValidity(&self, can_gc: CanGc) -> bool { |
| self.check_validity(can_gc) |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity |
| fn ReportValidity(&self, can_gc: CanGc) -> bool { |
| self.report_validity(can_gc) |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage |
| fn ValidationMessage(&self) -> DOMString { |
| self.validation_message() |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity |
| fn SetCustomValidity(&self, error: DOMString) { |
| self.validity_state().set_custom_error_message(error); |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#dom-fieldset-type> |
| fn Type(&self) -> DOMString { |
| DOMString::from_string(String::from("fieldset")) |
| } |
| } |
| |
| impl VirtualMethods for HTMLFieldSetElement { |
| fn super_type(&self) -> Option<&dyn VirtualMethods> { |
| Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) |
| } |
| |
| fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { |
| self.super_type() |
| .unwrap() |
| .attribute_mutated(attr, mutation, can_gc); |
| match *attr.local_name() { |
| local_name!("disabled") => { |
| let disabled_state = match mutation { |
| AttributeMutation::Set(None) => true, |
| AttributeMutation::Set(Some(_)) => { |
| // Fieldset was already disabled before. |
| return; |
| }, |
| AttributeMutation::Removed => false, |
| }; |
| let node = self.upcast::<Node>(); |
| let element = self.upcast::<Element>(); |
| element.set_disabled_state(disabled_state); |
| element.set_enabled_state(!disabled_state); |
| let mut found_legend = false; |
| let children = node.children().filter(|node| { |
| if found_legend { |
| true |
| } else if node.is::<HTMLLegendElement>() { |
| found_legend = true; |
| false |
| } else { |
| true |
| } |
| }); |
| let fields = children.flat_map(|child| { |
| child |
| .traverse_preorder(ShadowIncluding::No) |
| .filter(|descendant| match descendant.type_id() { |
| NodeTypeId::Element(ElementTypeId::HTMLElement( |
| HTMLElementTypeId::HTMLButtonElement | |
| HTMLElementTypeId::HTMLInputElement | |
| HTMLElementTypeId::HTMLSelectElement | |
| HTMLElementTypeId::HTMLTextAreaElement, |
| )) => true, |
| NodeTypeId::Element(ElementTypeId::HTMLElement( |
| HTMLElementTypeId::HTMLElement, |
| )) => descendant |
| .downcast::<HTMLElement>() |
| .unwrap() |
| .is_form_associated_custom_element(), |
| _ => false, |
| }) |
| }); |
| if disabled_state { |
| for field in fields { |
| let element = field.downcast::<Element>().unwrap(); |
| if element.enabled_state() { |
| element.set_disabled_state(true); |
| element.set_enabled_state(false); |
| if element |
| .downcast::<HTMLElement>() |
| .is_some_and(|h| h.is_form_associated_custom_element()) |
| { |
| ScriptThread::enqueue_callback_reaction( |
| element, |
| CallbackReaction::FormDisabled(true), |
| None, |
| ); |
| } |
| } |
| element.update_sequentially_focusable_status(can_gc); |
| } |
| } else { |
| for field in fields { |
| let element = field.downcast::<Element>().unwrap(); |
| if element.disabled_state() { |
| element.check_disabled_attribute(); |
| element.check_ancestors_disabled_state_for_form_control(); |
| // Fire callback only if this has actually enabled the custom element |
| if element.enabled_state() && |
| element |
| .downcast::<HTMLElement>() |
| .is_some_and(|h| h.is_form_associated_custom_element()) |
| { |
| ScriptThread::enqueue_callback_reaction( |
| element, |
| CallbackReaction::FormDisabled(false), |
| None, |
| ); |
| } |
| } |
| element.update_sequentially_focusable_status(can_gc); |
| } |
| } |
| element.update_sequentially_focusable_status(can_gc); |
| }, |
| local_name!("form") => { |
| self.form_attribute_mutated(mutation, can_gc); |
| }, |
| _ => {}, |
| } |
| } |
| } |
| |
| impl FormControl for HTMLFieldSetElement { |
| fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { |
| self.form_owner.get() |
| } |
| |
| fn set_form_owner(&self, form: Option<&HTMLFormElement>) { |
| self.form_owner.set(form); |
| } |
| |
| fn to_element(&self) -> &Element { |
| self.upcast::<Element>() |
| } |
| } |
| |
| impl Validatable for HTMLFieldSetElement { |
| fn as_element(&self) -> &Element { |
| self.upcast() |
| } |
| |
| fn validity_state(&self) -> DomRoot<ValidityState> { |
| self.validity_state |
| .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note())) |
| } |
| |
| fn is_instance_validatable(&self) -> bool { |
| // fieldset is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit) |
| false |
| } |
| } |