diff options
Diffstat (limited to 'components/script/dom/htmlelement.rs')
-rw-r--r-- | components/script/dom/htmlelement.rs | 244 |
1 files changed, 215 insertions, 29 deletions
diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 1eca57e2b60..4b62672e07e 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -22,28 +22,34 @@ use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMeth use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; +use crate::dom::customelementregistry::CallbackReaction; use crate::dom::document::{Document, FocusType}; use crate::dom::documentfragment::DocumentFragment; use crate::dom::domstringmap::DOMStringMap; use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::elementinternals::ElementInternals; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::htmlbodyelement::HTMLBodyElement; use crate::dom::htmlbrelement::HTMLBRElement; use crate::dom::htmldetailselement::HTMLDetailsElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; use crate::dom::htmlframesetelement::HTMLFrameSetElement; use crate::dom::htmlhtmlelement::HTMLHtmlElement; use crate::dom::htmlinputelement::{HTMLInputElement, InputType}; use crate::dom::htmllabelelement::HTMLLabelElement; use crate::dom::htmltextareaelement::HTMLTextAreaElement; -use crate::dom::node::{document_from_node, window_from_node, Node, ShadowIncluding}; +use crate::dom::node::{ + document_from_node, window_from_node, BindContext, Node, ShadowIncluding, UnbindContext, +}; use crate::dom::text::Text; use crate::dom::virtualmethods::VirtualMethods; +use crate::script_thread::ScriptThread; #[dom_struct] pub struct HTMLElement { @@ -352,7 +358,7 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-click fn Click(&self) { - let element = self.upcast::<Element>(); + let element = self.as_element(); if element.disabled_state() { return; } @@ -377,7 +383,7 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-blur fn Blur(&self) { // TODO: Run the unfocusing steps. - if !self.upcast::<Element>().focus_state() { + if !self.as_element().focus_state() { return; } // https://html.spec.whatwg.org/multipage/#unfocusing-steps @@ -446,7 +452,7 @@ impl HTMLElementMethods for HTMLElement { fn InnerText(&self) -> DOMString { let node = self.upcast::<Node>(); let window = window_from_node(node); - let element = self.upcast::<Element>(); + let element = self.as_element(); // Step 1. let element_not_rendered = !node.is_connected() || !element.has_css_layout_box(); @@ -511,12 +517,12 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-translate fn Translate(&self) -> bool { - self.upcast::<Element>().is_translate_enabled() + self.as_element().is_translate_enabled() } // https://html.spec.whatwg.org/multipage/#dom-translate fn SetTranslate(&self, yesno: bool) { - self.upcast::<Element>().set_string_attribute( + self.as_element().set_string_attribute( &html5ever::local_name!("translate"), match yesno { true => DOMString::from("yes"), @@ -528,7 +534,7 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-contenteditable fn ContentEditable(&self) -> DOMString { // TODO: https://github.com/servo/servo/issues/12776 - self.upcast::<Element>() + self.as_element() .get_attribute(&ns!(), &local_name!("contenteditable")) .map(|attr| DOMString::from(&**attr.value())) .unwrap_or_else(|| DOMString::from("inherit")) @@ -545,6 +551,46 @@ impl HTMLElementMethods for HTMLElement { // TODO: https://github.com/servo/servo/issues/12776 false } + /// <https://html.spec.whatwg.org/multipage#dom-attachinternals> + fn AttachInternals(&self) -> Fallible<DomRoot<ElementInternals>> { + let element = self.as_element(); + // Step 1: If this's is value is not null, then throw a "NotSupportedError" DOMException + if element.get_is().is_some() { + return Err(Error::NotSupported); + } + + // Step 2: Let definition be the result of looking up a custom element definition + // Note: the element can pass this check without yet being a custom + // element, as long as there is a registered definition + // that could upgrade it to one later. + let registry = document_from_node(self).window().CustomElements(); + let definition = registry.lookup_definition(self.as_element().local_name(), None); + + // Step 3: If definition is null, then throw an "NotSupportedError" DOMException + let definition = match definition { + Some(definition) => definition, + None => return Err(Error::NotSupported), + }; + + // Step 4: If definition's disable internals is true, then throw a "NotSupportedError" DOMException + if definition.disable_internals { + return Err(Error::NotSupported); + } + + // Step 5: If this's attached internals is non-null, then throw an "NotSupportedError" DOMException + let internals = element.ensure_element_internals(); + if internals.attached() { + return Err(Error::NotSupported); + } + + if self.is_form_associated_custom_element() { + element.init_state_for_internals(); + } + + // Step 6-7: Set this's attached internals to a new ElementInternals instance + internals.set_attached(); + Ok(internals) + } } fn append_text_node_to_fragment(document: &Document, fragment: &DocumentFragment, text: String) { @@ -620,14 +666,14 @@ impl HTMLElement { { return Err(Error::Syntax); } - self.upcast::<Element>() + self.as_element() .set_custom_attribute(to_snake_case(name), value) } pub fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> { // FIXME(ajeffrey): Convert directly from DOMString to LocalName let local_name = LocalName::from(to_snake_case(local_name)); - self.upcast::<Element>() + self.as_element() .get_attribute(&ns!(), &local_name) .map(|attr| { DOMString::from(&**attr.value()) // FIXME(ajeffrey): Convert directly from AttrValue to DOMString @@ -637,11 +683,10 @@ impl HTMLElement { pub fn delete_custom_attr(&self, local_name: DOMString) { // FIXME(ajeffrey): Convert directly from DOMString to LocalName let local_name = LocalName::from(to_snake_case(local_name)); - self.upcast::<Element>() - .remove_attribute(&ns!(), &local_name); + self.as_element().remove_attribute(&ns!(), &local_name); } - // https://html.spec.whatwg.org/multipage/#category-label + /// <https://html.spec.whatwg.org/multipage/#category-label> pub fn is_labelable_element(&self) -> bool { match self.upcast::<Node>().type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id { @@ -654,31 +699,54 @@ impl HTMLElement { HTMLElementTypeId::HTMLProgressElement | HTMLElementTypeId::HTMLSelectElement | HTMLElementTypeId::HTMLTextAreaElement => true, - _ => false, + _ => self.is_form_associated_custom_element(), }, _ => false, } } - // https://html.spec.whatwg.org/multipage/#category-listed + /// <https://html.spec.whatwg.org/multipage/#form-associated-custom-element> + pub fn is_form_associated_custom_element(&self) -> bool { + if let Some(definition) = self.as_element().get_custom_element_definition() { + definition.is_autonomous() && definition.form_associated + } else { + false + } + } + + /// <https://html.spec.whatwg.org/multipage/#category-listed> pub fn is_listed_element(&self) -> bool { match self.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => matches!( - type_id, + NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id { HTMLElementTypeId::HTMLButtonElement | - HTMLElementTypeId::HTMLFieldSetElement | - HTMLElementTypeId::HTMLInputElement | - HTMLElementTypeId::HTMLObjectElement | - HTMLElementTypeId::HTMLOutputElement | - HTMLElementTypeId::HTMLSelectElement | - HTMLElementTypeId::HTMLTextAreaElement - ), + HTMLElementTypeId::HTMLFieldSetElement | + HTMLElementTypeId::HTMLInputElement | + HTMLElementTypeId::HTMLObjectElement | + HTMLElementTypeId::HTMLOutputElement | + HTMLElementTypeId::HTMLSelectElement | + HTMLElementTypeId::HTMLTextAreaElement => true, + _ => self.is_form_associated_custom_element(), + }, + _ => false, + } + } + + /// <https://html.spec.whatwg.org/multipage/#category-submit> + pub fn is_submittable_element(&self) -> bool { + match self.upcast::<Node>().type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id { + HTMLElementTypeId::HTMLButtonElement | + HTMLElementTypeId::HTMLInputElement | + HTMLElementTypeId::HTMLSelectElement | + HTMLElementTypeId::HTMLTextAreaElement => true, + _ => self.is_form_associated_custom_element(), + }, _ => false, } } pub fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> { - let element = self.upcast::<Element>(); + let element = self.as_element(); element .attrs() .iter() @@ -692,7 +760,7 @@ impl HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-lfe-labels // This gets the nth label in tree order. pub fn label_at(&self, index: u32) -> Option<DomRoot<Node>> { - let element = self.upcast::<Element>(); + let element = self.as_element(); // Traverse entire tree for <label> elements that have // this as their control. @@ -721,7 +789,7 @@ impl HTMLElement { // This counts the labels of the element, to support NodeList::Length pub fn labels_count(&self) -> u32 { // see label_at comments about performance - let element = self.upcast::<Element>(); + let element = self.as_element(); let root_element = element.root_element(); let root_node = root_element.upcast::<Node>(); root_node @@ -814,7 +882,7 @@ impl HTMLElement { .child_elements() .find(|el| el.local_name() == &local_name!("summary")); match first_summary_element { - Some(first_summary) => &*first_summary == self.upcast::<Element>(), + Some(first_summary) => &*first_summary == self.as_element(), None => false, } } @@ -822,11 +890,12 @@ impl HTMLElement { impl VirtualMethods for HTMLElement { fn super_type(&self) -> Option<&dyn VirtualMethods> { - Some(self.upcast::<Element>() as &dyn VirtualMethods) + Some(self.as_element() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); + let element = self.as_element(); match (attr.local_name(), mutation) { (name, AttributeMutation::Set(_)) if name.starts_with("on") => { let evtarget = self.upcast::<EventTarget>(); @@ -839,10 +908,95 @@ impl VirtualMethods for HTMLElement { DOMString::from(&**attr.value()), ); }, + (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => { + self.form_attribute_mutated(mutation); + }, + // Adding a "disabled" attribute disables an enabled form element. + (&local_name!("disabled"), AttributeMutation::Set(_)) + if self.is_form_associated_custom_element() && element.enabled_state() => + { + element.set_disabled_state(true); + element.set_enabled_state(false); + ScriptThread::enqueue_callback_reaction( + element, + CallbackReaction::FormDisabled(true), + None, + ); + }, + // Removing the "disabled" attribute may enable a disabled + // form element, but a fieldset ancestor may keep it disabled. + (&local_name!("disabled"), AttributeMutation::Removed) + if self.is_form_associated_custom_element() && element.disabled_state() => + { + element.set_disabled_state(false); + element.set_enabled_state(true); + element.check_ancestors_disabled_state_for_form_control(); + if element.enabled_state() { + ScriptThread::enqueue_callback_reaction( + element, + CallbackReaction::FormDisabled(false), + None, + ); + } + }, + (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => { + match mutation { + AttributeMutation::Set(_) => { + element.set_read_write_state(true); + }, + AttributeMutation::Removed => { + element.set_read_write_state(false); + }, + } + }, _ => {}, } } + fn bind_to_tree(&self, context: &BindContext) { + if let Some(ref super_type) = self.super_type() { + super_type.bind_to_tree(context); + } + let element = self.as_element(); + element.update_sequentially_focusable_status(); + + // Binding to a tree can disable a form control if one of the new + // ancestors is a fieldset. + if self.is_form_associated_custom_element() && element.enabled_state() { + element.check_ancestors_disabled_state_for_form_control(); + if element.disabled_state() { + ScriptThread::enqueue_callback_reaction( + element, + CallbackReaction::FormDisabled(true), + None, + ); + } + } + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + if let Some(ref super_type) = self.super_type() { + super_type.unbind_from_tree(context); + } + + // Unbinding from a tree might enable a form control, if a + // fieldset ancestor is the only reason it was disabled. + // (The fact that it's enabled doesn't do much while it's + // disconnected, but it is an observable fact to keep track of.) + let element = self.as_element(); + if self.is_form_associated_custom_element() && element.disabled_state() { + element.check_disabled_attribute(); + element.check_ancestors_disabled_state_for_form_control(); + if element.enabled_state() { + ScriptThread::enqueue_callback_reaction( + element, + CallbackReaction::FormDisabled(false), + None, + ); + } + } + } + fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match *name { local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()), @@ -869,3 +1023,35 @@ impl Activatable for HTMLElement { self.summary_activation_behavior(); } } +// Form-associated custom elements are the same interface type as +// normal HTMLElements, so HTMLElement needs to have the FormControl trait +// even though it's usually more specific trait implementations, like the +// HTMLInputElement one, that we really want. (Alternately we could put +// the FormControl trait on ElementInternals, but that raises lifetime issues.) +impl FormControl for HTMLElement { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { + debug_assert!(self.is_form_associated_custom_element()); + self.as_element() + .get_element_internals() + .and_then(|e| e.form_owner()) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + debug_assert!(self.is_form_associated_custom_element()); + self.as_element() + .ensure_element_internals() + .set_form_owner(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + debug_assert!(self.is_form_associated_custom_element()); + self.as_element() + } + + fn is_listed(&self) -> bool { + debug_assert!(self.is_form_associated_custom_element()); + true + } + + // TODO candidate_for_validation, satisfies_constraints traits +} |