use std::collections::HashSet;
use std::default::Default;
use std::rc::Rc;
use dom_struct::dom_struct;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
use js::rust::HandleObject;
use script_layout_interface::QueryMsg;
use style::attr::AttrValue;
use style_dom::ElementState;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
EventHandlerNonNull, OnErrorEventHandlerNonNull,
};
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
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, 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::characterdata::CharacterData;
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, BindContext, Node, ShadowIncluding, UnbindContext,
};
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
#[dom_struct]
pub struct HTMLElement {
element: Element,
style_decl: MutNullableDom<CSSStyleDeclaration>,
dataset: MutNullableDom<DOMStringMap>,
}
impl HTMLElement {
pub fn new_inherited(
tag_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLElement {
HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
}
pub fn new_inherited_with_state(
state: ElementState,
tag_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLElement {
HTMLElement {
element: Element::new_inherited_with_state(
state,
tag_name,
ns!(html),
prefix,
document,
),
style_decl: Default::default(),
dataset: Default::default(),
}
}
#[allow(crown::unrooted_must_root)]
pub fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLElement> {
Node::reflect_node_with_proto(
Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
fn is_body_or_frameset(&self) -> bool {
let eventtarget = self.upcast::<EventTarget>();
eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
}
fn get_inner_outer_text(&self, can_gc: CanGc) -> DOMString {
let node = self.upcast::<Node>();
let window = window_from_node(node);
let element = self.as_element();
let element_not_rendered = !node.is_connected() || !element.has_css_layout_box(can_gc);
if element_not_rendered {
return node.GetTextContent().unwrap();
}
window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery, can_gc);
let text = window
.layout()
.query_element_inner_outer_text(node.to_trusted_node_address());
DOMString::from(text)
}
}
impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
fn Style(&self) -> DomRoot<CSSStyleDeclaration> {
self.style_decl.or_init(|| {
let global = window_from_node(self);
CSSStyleDeclaration::new(
&global,
CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
None,
CSSModificationAccess::ReadWrite,
)
})
}
make_getter!(Title, "title");
make_setter!(SetTitle, "title");
make_getter!(Lang, "lang");
make_setter!(SetLang, "lang");
make_enumerated_getter!(
Dir,
"dir",
"ltr" | "rtl" | "auto",
missing => "",
invalid => ""
);
make_setter!(SetDir, "dir");
make_bool_getter!(Hidden, "hidden");
make_bool_setter!(SetHidden, "hidden");
global_event_handlers!(NoOnload);
document_and_element_event_handlers!();
fn Dataset(&self) -> DomRoot<DOMStringMap> {
self.dataset.or_init(|| DOMStringMap::new(self))
}
fn GetOnerror(&self, can_gc: CanGc) -> Option<Rc<OnErrorEventHandlerNonNull>> {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().GetOnerror()
} else {
None
}
} else {
self.upcast::<EventTarget>()
.get_event_handler_common("error", can_gc)
}
}
fn SetOnerror(&self, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().SetOnerror(listener)
}
} else {
self.upcast::<EventTarget>()
.set_error_event_handler("error", listener)
}
}
fn GetOnload(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().GetOnload()
} else {
None
}
} else {
self.upcast::<EventTarget>()
.get_event_handler_common("load", can_gc)
}
}
fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().SetOnload(listener)
}
} else {
self.upcast::<EventTarget>()
.set_event_handler_common("load", listener)
}
}
fn GetOnblur(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().GetOnblur()
} else {
None
}
} else {
self.upcast::<EventTarget>()
.get_event_handler_common("blur", can_gc)
}
}
fn SetOnblur(&self, listener: Option<Rc<EventHandlerNonNull>>) {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().SetOnblur(listener)
}
} else {
self.upcast::<EventTarget>()
.set_event_handler_common("blur", listener)
}
}
fn GetOnfocus(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().GetOnfocus()
} else {
None
}
} else {
self.upcast::<EventTarget>()
.get_event_handler_common("focus", can_gc)
}
}
fn SetOnfocus(&self, listener: Option<Rc<EventHandlerNonNull>>) {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().SetOnfocus(listener)
}
} else {
self.upcast::<EventTarget>()
.set_event_handler_common("focus", listener)
}
}
fn GetOnresize(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().GetOnresize()
} else {
None
}
} else {
self.upcast::<EventTarget>()
.get_event_handler_common("resize", can_gc)
}
}
fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().SetOnresize(listener)
}
} else {
self.upcast::<EventTarget>()
.set_event_handler_common("resize", listener)
}
}
fn GetOnscroll(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().GetOnscroll()
} else {
None
}
} else {
self.upcast::<EventTarget>()
.get_event_handler_common("scroll", can_gc)
}
}
fn SetOnscroll(&self, listener: Option<Rc<EventHandlerNonNull>>) {
if self.is_body_or_frameset() {
let document = document_from_node(self);
if document.has_browsing_context() {
document.window().SetOnscroll(listener)
}
} else {
self.upcast::<EventTarget>()
.set_event_handler_common("scroll", listener)
}
}
fn Itemtypes(&self) -> Option<Vec<DOMString>> {
let atoms = self
.element
.get_tokenlist_attribute(&local_name!("itemtype"));
if atoms.is_empty() {
return None;
}
let mut item_attr_values = HashSet::new();
for attr_value in &atoms {
item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
}
Some(item_attr_values.into_iter().collect())
}
fn PropertyNames(&self) -> Option<Vec<DOMString>> {
let atoms = self
.element
.get_tokenlist_attribute(&local_name!("itemprop"));
if atoms.is_empty() {
return None;
}
let mut item_attr_values = HashSet::new();
for attr_value in &atoms {
item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
}
Some(item_attr_values.into_iter().collect())
}
fn Click(&self, can_gc: CanGc) {
let element = self.as_element();
if element.disabled_state() {
return;
}
if element.click_in_progress() {
return;
}
element.set_click_in_progress(true);
self.upcast::<Node>()
.fire_synthetic_mouse_event_not_trusted(DOMString::from("click"), can_gc);
element.set_click_in_progress(false);
}
fn Focus(&self, can_gc: CanGc) {
let document = document_from_node(self);
document.request_focus(Some(self.upcast()), FocusType::Element, can_gc);
}
fn Blur(&self, can_gc: CanGc) {
if !self.as_element().focus_state() {
return;
}
let document = document_from_node(self);
document.request_focus(None, FocusType::Element, can_gc);
}
fn GetOffsetParent(&self, can_gc: CanGc) -> Option<DomRoot<Element>> {
if self.is::<HTMLBodyElement>() || self.is::<HTMLHtmlElement>() {
return None;
}
let node = self.upcast::<Node>();
let window = window_from_node(self);
let (element, _) = window.offset_parent_query(node, can_gc);
element
}
fn OffsetTop(&self, can_gc: CanGc) -> i32 {
if self.is::<HTMLBodyElement>() {
return 0;
}
let node = self.upcast::<Node>();
let window = window_from_node(self);
let (_, rect) = window.offset_parent_query(node, can_gc);
rect.origin.y.to_nearest_px()
}
fn OffsetLeft(&self, can_gc: CanGc) -> i32 {
if self.is::<HTMLBodyElement>() {
return 0;
}
let node = self.upcast::<Node>();
let window = window_from_node(self);
let (_, rect) = window.offset_parent_query(node, can_gc);
rect.origin.x.to_nearest_px()
}
fn OffsetWidth(&self, can_gc: CanGc) -> i32 {
let node = self.upcast::<Node>();
let window = window_from_node(self);
let (_, rect) = window.offset_parent_query(node, can_gc);
rect.size.width.to_nearest_px()
}
fn OffsetHeight(&self, can_gc: CanGc) -> i32 {
let node = self.upcast::<Node>();
let window = window_from_node(self);
let (_, rect) = window.offset_parent_query(node, can_gc);
rect.size.height.to_nearest_px()
}
fn InnerText(&self, can_gc: CanGc) -> DOMString {
self.get_inner_outer_text(can_gc)
}
fn SetInnerText(&self, input: DOMString, can_gc: CanGc) {
let fragment = self.rendered_text_fragment(input, can_gc);
Node::replace_all(Some(fragment.upcast()), self.upcast::<Node>());
}
fn GetOuterText(&self, can_gc: CanGc) -> Fallible<DOMString> {
Ok(self.get_inner_outer_text(can_gc))
}
fn SetOuterText(&self, input: DOMString, can_gc: CanGc) -> Fallible<()> {
let Some(parent) = self.upcast::<Node>().GetParentNode() else {
return Err(Error::NoModificationAllowed);
};
let node = self.upcast::<Node>();
let document = document_from_node(self);
let next = node.GetNextSibling();
let previous = node.GetPreviousSibling();
let fragment = self.rendered_text_fragment(input, can_gc);
if fragment.upcast::<Node>().children_count() == 0 {
let text_node = Text::new(DOMString::from("".to_owned()), &document, can_gc);
fragment.upcast::<Node>().AppendChild(text_node.upcast())?;
}
parent.ReplaceChild(fragment.upcast(), node)?;
if let Some(next_sibling) = next {
if let Some(node) = next_sibling.GetPreviousSibling() {
Self::merge_with_the_next_text_node(node);
}
}
if let Some(previous) = previous {
Self::merge_with_the_next_text_node(previous)
}
Ok(())
}
fn Translate(&self) -> bool {
self.as_element().is_translate_enabled()
}
fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
self.as_element().set_string_attribute(
&html5ever::local_name!("translate"),
match yesno {
true => DOMString::from("yes"),
false => DOMString::from("no"),
},
can_gc,
);
}
fn ContentEditable(&self) -> DOMString {
self.as_element()
.get_attribute(&ns!(), &local_name!("contenteditable"))
.map(|attr| DOMString::from(&**attr.value()))
.unwrap_or_else(|| DOMString::from("inherit"))
}
fn SetContentEditable(&self, _: DOMString) {
warn!("The contentEditable attribute is not implemented yet");
}
fn IsContentEditable(&self) -> bool {
false
}
fn AttachInternals(&self) -> Fallible<DomRoot<ElementInternals>> {
let element = self.as_element();
if element.get_is().is_some() {
return Err(Error::NotSupported);
}
let registry = document_from_node(self).window().CustomElements();
let definition = registry.lookup_definition(self.as_element().local_name(), None);
let definition = match definition {
Some(definition) => definition,
None => return Err(Error::NotSupported),
};
if definition.disable_internals {
return Err(Error::NotSupported);
}
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();
}
internals.set_attached();
Ok(internals)
}
fn Autofocus(&self) -> bool {
self.element.has_attribute(&local_name!("autofocus"))
}
fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
self.element
.set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
}
}
fn append_text_node_to_fragment(
document: &Document,
fragment: &DocumentFragment,
text: String,
can_gc: CanGc,
) {
let text = Text::new(DOMString::from(text), document, can_gc);
fragment
.upcast::<Node>()
.AppendChild(text.upcast())
.unwrap();
}
static DATA_PREFIX: &str = "data-";
static DATA_HYPHEN_SEPARATOR: char = '\x2d';
fn to_snake_case(name: DOMString) -> DOMString {
let mut attr_name = String::with_capacity(name.len() + DATA_PREFIX.len());
attr_name.push_str(DATA_PREFIX);
for ch in name.chars() {
if ch.is_ascii_uppercase() {
attr_name.push(DATA_HYPHEN_SEPARATOR);
attr_name.push(ch.to_ascii_lowercase());
} else {
attr_name.push(ch);
}
}
DOMString::from(attr_name)
}
fn to_camel_case(name: &str) -> Option<DOMString> {
if !name.starts_with(DATA_PREFIX) {
return None;
}
let name = &name[5..];
let has_uppercase = name.chars().any(|curr_char| curr_char.is_ascii_uppercase());
if has_uppercase {
return None;
}
let mut result = String::with_capacity(name.len().saturating_sub(DATA_PREFIX.len()));
let mut name_chars = name.chars();
while let Some(curr_char) = name_chars.next() {
if curr_char == DATA_HYPHEN_SEPARATOR {
if let Some(next_char) = name_chars.next() {
if next_char.is_ascii_lowercase() {
result.push(next_char.to_ascii_uppercase());
} else {
result.push(curr_char);
result.push(next_char);
}
} else {
result.push(curr_char);
}
} else {
result.push(curr_char);
}
}
Some(DOMString::from(result))
}
impl HTMLElement {
pub fn set_custom_attr(&self, name: DOMString, value: DOMString, can_gc: CanGc) -> ErrorResult {
if name
.chars()
.skip_while(|&ch| ch != '\u{2d}')
.nth(1)
.is_some_and(|ch| ch.is_ascii_lowercase())
{
return Err(Error::Syntax);
}
self.as_element()
.set_custom_attribute(to_snake_case(name), value, can_gc)
}
pub fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> {
let local_name = LocalName::from(to_snake_case(local_name));
self.as_element()
.get_attribute(&ns!(), &local_name)
.map(|attr| {
DOMString::from(&**attr.value()) })
}
pub fn delete_custom_attr(&self, local_name: DOMString) {
let local_name = LocalName::from(to_snake_case(local_name));
self.as_element().remove_attribute(&ns!(), &local_name);
}
pub fn is_labelable_element(&self) -> bool {
match self.upcast::<Node>().type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
HTMLElementTypeId::HTMLInputElement => {
self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
},
HTMLElementTypeId::HTMLButtonElement |
HTMLElementTypeId::HTMLMeterElement |
HTMLElementTypeId::HTMLOutputElement |
HTMLElementTypeId::HTMLProgressElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement => true,
_ => self.is_form_associated_custom_element(),
},
_ => false,
}
}
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
}
}
pub fn is_listed_element(&self) -> bool {
match self.upcast::<Node>().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 => true,
_ => self.is_form_associated_custom_element(),
},
_ => false,
}
}
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.as_element();
element
.attrs()
.iter()
.filter_map(|attr| {
let raw_name = attr.local_name();
to_camel_case(raw_name)
})
.collect()
}
pub fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
let element = self.as_element();
let root_element = element.root_element();
let root_node = root_element.upcast::<Node>();
root_node
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<HTMLLabelElement>)
.filter(|elem| match elem.GetControl() {
Some(control) => &*control == self,
_ => false,
})
.nth(index as usize)
.map(|n| DomRoot::from_ref(n.upcast::<Node>()))
}
pub fn labels_count(&self) -> u32 {
let element = self.as_element();
let root_element = element.root_element();
let root_node = root_element.upcast::<Node>();
root_node
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<HTMLLabelElement>)
.filter(|elem| match elem.GetControl() {
Some(control) => &*control == self,
_ => false,
})
.count() as u32
}
pub fn directionality(&self) -> Option<String> {
let element_direction: &str = &self.Dir();
if element_direction == "ltr" {
return Some("ltr".to_owned());
}
if element_direction == "rtl" {
return Some("rtl".to_owned());
}
if let Some(input) = self.downcast::<HTMLInputElement>() {
if input.input_type() == InputType::Tel {
return Some("ltr".to_owned());
}
}
if element_direction == "auto" {
if let Some(directionality) = self
.downcast::<HTMLInputElement>()
.and_then(|input| input.auto_directionality())
{
return Some(directionality);
}
if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
return Some(area.auto_directionality());
}
}
None
}
pub fn summary_activation_behavior(&self) {
if !self.is_summary_for_its_parent_details() {
return;
}
let parent_details = self.upcast::<Node>().GetParentNode().unwrap();
parent_details
.downcast::<HTMLDetailsElement>()
.unwrap()
.toggle();
}
fn is_summary_for_its_parent_details(&self) -> bool {
let summary_node = self.upcast::<Node>();
if !summary_node.has_parent() {
return false;
}
let parent = &summary_node.GetParentNode().unwrap();
if !parent.is::<HTMLDetailsElement>() {
return false;
}
let first_summary_element = parent
.child_elements()
.find(|el| el.local_name() == &local_name!("summary"));
match first_summary_element {
Some(first_summary) => &*first_summary == self.as_element(),
None => false,
}
}
fn rendered_text_fragment(&self, input: DOMString, can_gc: CanGc) -> DomRoot<DocumentFragment> {
let document = document_from_node(self);
let fragment = DocumentFragment::new(&document, can_gc);
let mut position = input.chars().peekable();
let mut text = String::new();
while let Some(ch) = position.next() {
match ch {
'\u{000A}' | '\u{000D}' => {
if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
position.next();
}
if !text.is_empty() {
append_text_node_to_fragment(&document, &fragment, text, can_gc);
text = String::new();
}
let br = HTMLBRElement::new(local_name!("br"), None, &document, None, can_gc);
fragment.upcast::<Node>().AppendChild(br.upcast()).unwrap();
},
_ => {
text.push(ch);
},
}
}
if !text.is_empty() {
append_text_node_to_fragment(&document, &fragment, text, can_gc);
}
fragment
}
fn merge_with_the_next_text_node(node: DomRoot<Node>) {
if !node.is::<Text>() {
return;
}
let next = match node.GetNextSibling() {
Some(next) => next,
None => return,
};
if !next.is::<Text>() {
return;
}
let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
node_chars
.ReplaceData(node_chars.Length(), 0, next_chars.Data())
.expect("Got chars from Text");
next.remove_self();
}
}
impl VirtualMethods for HTMLElement {
fn super_type(&self) -> Option<&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>();
let source_line = 1; evtarget.set_event_handler_uncompiled(
window_from_node(self).get_url(),
source_line,
&name[2..],
DOMString::from(&**attr.value()),
);
},
(&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
self.form_attribute_mutated(mutation);
},
(&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,
);
},
(&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(super_type) = self.super_type() {
super_type.bind_to_tree(context);
}
let element = self.as_element();
element.update_sequentially_focusable_status(CanGc::note());
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(super_type) = self.super_type() {
super_type.unbind_from_tree(context);
}
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()),
local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
}
impl Activatable for HTMLElement {
fn as_element(&self) -> &Element {
self.upcast::<Element>()
}
fn is_instance_activatable(&self) -> bool {
self.as_element().local_name() == &local_name!("summary")
}
fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
self.summary_activation_behavior();
}
}
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(&self) -> &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
}
}