servo/components/script/dom/htmlelement.rs
Martin Robinson b7d089930e
layout: Remove LayoutRPC and query layout via the Layout trait (#31937)
Instead of the tricky `LayoutRPC` interface, query layout using the
`Layout` trait. This means that now queries will requires calling layout
and then running the query. During layout an enum is used to indicate
what kind of layout is necessary.

This change also removes the mutex-locked `rw_data` from both layout
threads. It's no longer necessary since layout runs synchronously. The
one downside here is that for resolved style queries, we now have to
create two StyleContexts. One for layout and one for the query itself.
The creation of this context should not be very expensive though.

`LayoutRPC` used to be necessary because layout used to run
asynchronously from script, but that no longer happens. With this
change, it becomes possible to safely pass nodes to layout from script
-- a cleanup that can happen in a followup change.
2024-03-29 16:25:47 +00:00

871 lines
30 KiB
Rust

/* 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::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::message::QueryMsg;
use style::attr::AttrValue;
use style_traits::dom::ElementState;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
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};
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::document::{Document, FocusType};
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::domstringmap::DOMStringMap;
use crate::dom::element::{AttributeMutation, Element};
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::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::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
#[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>,
) -> DomRoot<HTMLElement> {
Node::reflect_node_with_proto(
Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
document,
proto,
)
}
fn is_body_or_frameset(&self) -> bool {
let eventtarget = self.upcast::<EventTarget>();
eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
}
}
impl HTMLElementMethods for HTMLElement {
// https://html.spec.whatwg.org/multipage/#the-style-attribute
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,
)
})
}
// https://html.spec.whatwg.org/multipage/#attr-title
make_getter!(Title, "title");
// https://html.spec.whatwg.org/multipage/#attr-title
make_setter!(SetTitle, "title");
// https://html.spec.whatwg.org/multipage/#attr-lang
make_getter!(Lang, "lang");
// https://html.spec.whatwg.org/multipage/#attr-lang
make_setter!(SetLang, "lang");
// https://html.spec.whatwg.org/multipage/#the-dir-attribute
make_enumerated_getter!(Dir, "dir", "", "ltr" | "rtl" | "auto");
// https://html.spec.whatwg.org/multipage/#the-dir-attribute
make_setter!(SetDir, "dir");
// https://html.spec.whatwg.org/multipage/#dom-hidden
make_bool_getter!(Hidden, "hidden");
// https://html.spec.whatwg.org/multipage/#dom-hidden
make_bool_setter!(SetHidden, "hidden");
// https://html.spec.whatwg.org/multipage/#globaleventhandlers
global_event_handlers!(NoOnload);
// https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers
document_and_element_event_handlers!();
// https://html.spec.whatwg.org/multipage/#dom-dataset
fn Dataset(&self) -> DomRoot<DOMStringMap> {
self.dataset.or_init(|| DOMStringMap::new(self))
}
// https://html.spec.whatwg.org/multipage/#handler-onerror
fn GetOnerror(&self) -> 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")
}
}
// https://html.spec.whatwg.org/multipage/#handler-onerror
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 {
// special setter for error
self.upcast::<EventTarget>()
.set_error_event_handler("error", listener)
}
}
// https://html.spec.whatwg.org/multipage/#handler-onload
fn GetOnload(&self) -> 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")
}
}
// https://html.spec.whatwg.org/multipage/#handler-onload
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)
}
}
// https://html.spec.whatwg.org/multipage/#handler-onblur
fn GetOnblur(&self) -> 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")
}
}
// https://html.spec.whatwg.org/multipage/#handler-onblur
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)
}
}
// https://html.spec.whatwg.org/multipage/#handler-onfocus
fn GetOnfocus(&self) -> 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")
}
}
// https://html.spec.whatwg.org/multipage/#handler-onfocus
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)
}
}
// https://html.spec.whatwg.org/multipage/#handler-onresize
fn GetOnresize(&self) -> 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")
}
}
// https://html.spec.whatwg.org/multipage/#handler-onresize
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)
}
}
// https://html.spec.whatwg.org/multipage/#handler-onscroll
fn GetOnscroll(&self) -> 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")
}
}
// https://html.spec.whatwg.org/multipage/#handler-onscroll
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)
}
}
// https://html.spec.whatwg.org/multipage/#attr-itemtype
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())
}
// https://html.spec.whatwg.org/multipage/#names:-the-itemprop-attribute
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())
}
// https://html.spec.whatwg.org/multipage/#dom-click
fn Click(&self) {
let element = self.upcast::<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"));
element.set_click_in_progress(false);
}
// https://html.spec.whatwg.org/multipage/#dom-focus
fn Focus(&self) {
// TODO: Mark the element as locked for focus and run the focusing steps.
// https://html.spec.whatwg.org/multipage/#focusing-steps
let document = document_from_node(self);
document.request_focus(Some(self.upcast()), FocusType::Element);
}
// https://html.spec.whatwg.org/multipage/#dom-blur
fn Blur(&self) {
// TODO: Run the unfocusing steps.
if !self.upcast::<Element>().focus_state() {
return;
}
// https://html.spec.whatwg.org/multipage/#unfocusing-steps
let document = document_from_node(self);
document.request_focus(None, FocusType::Element);
}
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent
fn GetOffsetParent(&self) -> 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);
element
}
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsettop
fn OffsetTop(&self) -> 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);
rect.origin.y.to_nearest_px()
}
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetleft
fn OffsetLeft(&self) -> 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);
rect.origin.x.to_nearest_px()
}
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetwidth
fn OffsetWidth(&self) -> i32 {
let node = self.upcast::<Node>();
let window = window_from_node(self);
let (_, rect) = window.offset_parent_query(node);
rect.size.width.to_nearest_px()
}
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetheight
fn OffsetHeight(&self) -> i32 {
let node = self.upcast::<Node>();
let window = window_from_node(self);
let (_, rect) = window.offset_parent_query(node);
rect.size.height.to_nearest_px()
}
// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
fn InnerText(&self) -> DOMString {
let node = self.upcast::<Node>();
let window = window_from_node(node);
let element = self.upcast::<Element>();
// Step 1.
let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
if element_not_rendered {
return node.GetTextContent().unwrap();
}
window.layout_reflow(QueryMsg::ElementInnerTextQuery);
let text = window
.with_layout(|layout| layout.query_element_inner_text(node.to_trusted_node_address()))
.unwrap_or_default();
DOMString::from(text)
}
// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
fn SetInnerText(&self, input: DOMString) {
// Step 1.
let document = document_from_node(self);
// Step 2.
let fragment = DocumentFragment::new(&document);
// Step 3. The given value is already named 'input'.
// Step 4.
let mut position = input.chars().peekable();
// Step 5.
let mut text = String::new();
// Step 6.
while let Some(ch) = position.next() {
match ch {
'\u{000A}' | '\u{000D}' => {
if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
// a \r\n pair should only generate one <br>,
// so just skip the \r.
position.next();
}
if !text.is_empty() {
append_text_node_to_fragment(&document, &fragment, text);
text = String::new();
}
let br = HTMLBRElement::new(local_name!("br"), None, &document, None);
fragment.upcast::<Node>().AppendChild(br.upcast()).unwrap();
},
_ => {
text.push(ch);
},
}
}
if !text.is_empty() {
append_text_node_to_fragment(&document, &fragment, text);
}
// Step 7.
Node::replace_all(Some(fragment.upcast()), self.upcast::<Node>());
}
// https://html.spec.whatwg.org/multipage/#dom-translate
fn Translate(&self) -> bool {
self.upcast::<Element>().is_translate_enabled()
}
// https://html.spec.whatwg.org/multipage/#dom-translate
fn SetTranslate(&self, yesno: bool) {
self.upcast::<Element>().set_string_attribute(
&html5ever::local_name!("translate"),
match yesno {
true => DOMString::from("yes"),
false => DOMString::from("no"),
},
);
}
// https://html.spec.whatwg.org/multipage/#dom-contenteditable
fn ContentEditable(&self) -> DOMString {
// TODO: https://github.com/servo/servo/issues/12776
self.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("contenteditable"))
.map(|attr| DOMString::from(&**attr.value()))
.unwrap_or_else(|| DOMString::from("inherit"))
}
// https://html.spec.whatwg.org/multipage/#dom-contenteditable
fn SetContentEditable(&self, _: DOMString) {
// TODO: https://github.com/servo/servo/issues/12776
warn!("The contentEditable attribute is not implemented yet");
}
// https://html.spec.whatwg.org/multipage/#dom-contenteditable
fn IsContentEditable(&self) -> bool {
// TODO: https://github.com/servo/servo/issues/12776
false
}
}
fn append_text_node_to_fragment(document: &Document, fragment: &DocumentFragment, text: String) {
let text = Text::new(DOMString::from(text), document);
fragment
.upcast::<Node>()
.AppendChild(text.upcast())
.unwrap();
}
// https://html.spec.whatwg.org/multipage/#attr-data-*
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)
}
// https://html.spec.whatwg.org/multipage/#attr-data-*
// if this attribute is in snake case with a data- prefix,
// this function returns a name converted to camel case
// without the data prefix.
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() {
//check for hyphen followed by character
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) -> ErrorResult {
if name
.chars()
.skip_while(|&ch| ch != '\u{2d}')
.nth(1)
.map_or(false, |ch| ch.is_ascii_lowercase())
{
return Err(Error::Syntax);
}
self.upcast::<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>()
.get_attribute(&ns!(), &local_name)
.map(|attr| {
DOMString::from(&**attr.value()) // FIXME(ajeffrey): Convert directly from AttrValue to DOMString
})
}
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);
}
// 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 {
HTMLElementTypeId::HTMLInputElement => {
self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
},
HTMLElementTypeId::HTMLButtonElement |
HTMLElementTypeId::HTMLMeterElement |
HTMLElementTypeId::HTMLOutputElement |
HTMLElementTypeId::HTMLProgressElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement => true,
_ => false,
},
_ => 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,
HTMLElementTypeId::HTMLButtonElement |
HTMLElementTypeId::HTMLFieldSetElement |
HTMLElementTypeId::HTMLInputElement |
HTMLElementTypeId::HTMLObjectElement |
HTMLElementTypeId::HTMLOutputElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement
),
_ => false,
}
}
pub fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> {
let element = self.upcast::<Element>();
element
.attrs()
.iter()
.filter_map(|attr| {
let raw_name = attr.local_name();
to_camel_case(raw_name)
})
.collect()
}
// 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>();
// Traverse entire tree for <label> elements that have
// this as their control.
// There is room for performance optimization, as we don't need
// the actual result of GetControl, only whether the result
// would match self.
// (Even more room for performance optimization: do what
// nodelist ChildrenList does and keep a mutation-aware cursor
// around; this may be hard since labels need to keep working
// even as they get detached into a subtree and reattached to
// a document.)
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>()))
}
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
// 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 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
}
// https://html.spec.whatwg.org/multipage/#the-directionality.
// returns Some if can infer direction by itself or from child nodes
// returns None if requires to go up to parent
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());
}
}
// TODO(NeverHappened): Implement condition
// If the element's dir attribute is in the auto state OR
// If the element is a bdi element and the dir attribute is not in a defined state
// (i.e. it is not present or has an invalid value)
// Requires bdi element implementation (https://html.spec.whatwg.org/multipage/#the-bdi-element)
None
}
// https://html.spec.whatwg.org/multipage/#the-summary-element:activation-behaviour
pub fn summary_activation_behavior(&self) {
// Step 1
if !self.is_summary_for_its_parent_details() {
return;
}
// Step 2
let parent_details = self.upcast::<Node>().GetParentNode().unwrap();
// Step 3
parent_details
.downcast::<HTMLDetailsElement>()
.unwrap()
.toggle();
}
// https://html.spec.whatwg.org/multipage/#summary-for-its-parent-details
fn is_summary_for_its_parent_details(&self) -> bool {
// Step 1
let summary_node = self.upcast::<Node>();
if !summary_node.has_parent() {
return false;
}
// Step 2
let parent = &summary_node.GetParentNode().unwrap();
// Step 3
if !parent.is::<HTMLDetailsElement>() {
return false;
}
// Step 4 & 5
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.upcast::<Element>(),
None => false,
}
}
}
impl VirtualMethods for HTMLElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<Element>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
match (attr.local_name(), mutation) {
(name, AttributeMutation::Set(_)) if name.starts_with("on") => {
let evtarget = self.upcast::<EventTarget>();
let source_line = 1; //TODO(#9604) get current JS execution line
evtarget.set_event_handler_uncompiled(
window_from_node(self).get_url(),
source_line,
&name[2..],
// FIXME(ajeffrey): Convert directly from AttrValue to DOMString
DOMString::from(&**attr.value()),
);
},
_ => {},
}
}
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")
}
// Basically used to make the HTMLSummaryElement activatable (which has no IDL definition)
fn activation_behavior(&self, _event: &Event, _target: &EventTarget) {
self.summary_activation_behavior();
}
}