diff options
Diffstat (limited to 'components/script/dom/htmlelement.rs')
-rw-r--r-- | components/script/dom/htmlelement.rs | 742 |
1 files changed, 526 insertions, 216 deletions
diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index ade0b7307d1..0b1cb499fe2 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -1,38 +1,43 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::activation::{ActivationSource, synthetic_click_activation}; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; -use dom::bindings::codegen::Bindings::HTMLElementBinding; -use dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; -use dom::document::{Document, FocusType}; -use dom::domstringmap::DOMStringMap; -use dom::element::{AttributeMutation, Element}; -use dom::eventtarget::EventTarget; -use dom::htmlbodyelement::HTMLBodyElement; -use dom::htmlframesetelement::HTMLFrameSetElement; -use dom::htmlhtmlelement::HTMLHtmlElement; -use dom::htmlinputelement::HTMLInputElement; -use dom::htmllabelelement::HTMLLabelElement; -use dom::node::{Node, SEQUENTIALLY_FOCUSABLE}; -use dom::node::{document_from_node, window_from_node}; -use dom::nodelist::NodeList; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::activation::Activatable; +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::inheritance::{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}; +use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::text::Text; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use std::ascii::AsciiExt; -use std::borrow::ToOwned; +use html5ever::{LocalName, Prefix}; +use script_layout_interface::message::QueryMsg; +use std::collections::HashSet; use std::default::Default; use std::rc::Rc; use style::attr::AttrValue; @@ -41,85 +46,67 @@ use style::element_state::*; #[dom_struct] pub struct HTMLElement { element: Element, - style_decl: MutNullableJS<CSSStyleDeclaration>, - dataset: MutNullableJS<DOMStringMap>, + style_decl: MutNullableDom<CSSStyleDeclaration>, + dataset: MutNullableDom<DOMStringMap>, } impl HTMLElement { - pub fn new_inherited(tag_name: LocalName, prefix: Option<DOMString>, - document: &Document) -> 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<DOMString>, document: &Document) - -> HTMLElement { + 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), + element: Element::new_inherited_with_state( + state, + tag_name, + ns!(html), + prefix, + document, + ), style_decl: Default::default(), dataset: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> Root<HTMLElement> { - Node::reflect_node(box HTMLElement::new_inherited(local_name, prefix, document), - document, - HTMLElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLElement> { + Node::reflect_node( + Box::new(HTMLElement::new_inherited(local_name, prefix, document)), + document, + ) } fn is_body_or_frameset(&self) -> bool { let eventtarget = self.upcast::<EventTarget>(); eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>() } - - fn update_sequentially_focusable_status(&self) { - let element = self.upcast::<Element>(); - let node = self.upcast::<Node>(); - if element.has_attribute(&local_name!("tabindex")) { - node.set_flag(SEQUENTIALLY_FOCUSABLE, true); - } else { - match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) - => node.set_flag(SEQUENTIALLY_FOCUSABLE, true), - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) => { - if element.has_attribute(&local_name!("href")) { - node.set_flag(SEQUENTIALLY_FOCUSABLE, true); - } - }, - _ => { - if let Some(attr) = element.get_attribute(&ns!(), &local_name!("draggable")) { - let value = attr.value(); - let is_true = match *value { - AttrValue::String(ref string) => string == "true", - _ => false, - }; - node.set_flag(SEQUENTIALLY_FOCUSABLE, is_true); - } else { - node.set_flag(SEQUENTIALLY_FOCUSABLE, false); - } - //TODO set SEQUENTIALLY_FOCUSABLE flag if editing host - //TODO set SEQUENTIALLY_FOCUSABLE flag if "sorting interface th elements" - }, - } - } - } } impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#the-style-attribute - fn Style(&self) -> Root<CSSStyleDeclaration> { + fn Style(&self) -> DomRoot<CSSStyleDeclaration> { self.style_decl.or_init(|| { let global = window_from_node(self); - CSSStyleDeclaration::new(&global, - CSSStyleOwner::Element(JS::from_ref(self.upcast())), - None, - CSSModificationAccess::ReadWrite) + CSSStyleDeclaration::new( + &global, + CSSStyleOwner::Element(Dom::from_ref(self.upcast())), + None, + CSSModificationAccess::ReadWrite, + ) }) } @@ -138,6 +125,11 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-hidden make_bool_setter!(SetHidden, "hidden"); + // https://html.spec.whatwg.org/multipage/#the-dir-attribute + make_getter!(Dir, "dir"); + // https://html.spec.whatwg.org/multipage/#the-dir-attribute + make_setter!(SetDir, "dir"); + // https://html.spec.whatwg.org/multipage/#globaleventhandlers global_event_handlers!(NoOnload); @@ -145,38 +137,41 @@ impl HTMLElementMethods for HTMLElement { document_and_element_event_handlers!(); // https://html.spec.whatwg.org/multipage/#dom-dataset - fn Dataset(&self) -> Root<DOMStringMap> { + fn Dataset(&self) -> DomRoot<DOMStringMap> { self.dataset.or_init(|| DOMStringMap::new(self)) } - // https://html.spec.whatwg.org/multipage/#handler-onload - fn GetOnload(&self) -> Option<Rc<EventHandlerNonNull>> { + // 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().GetOnload() + document.window().GetOnerror() } else { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("load") + self.upcast::<EventTarget>() + .get_event_handler_common("error") } } - // https://html.spec.whatwg.org/multipage/#handler-onload - fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) { + // 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().SetOnload(listener) + document.window().SetOnerror(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("load", listener) + // special setter for error + self.upcast::<EventTarget>() + .set_error_event_handler("error", listener) } } - // https://html.spec.whatwg.org/multipage/#handler-onresize - fn GetOnresize(&self) -> Option<Rc<EventHandlerNonNull>> { + // 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() { @@ -185,19 +180,21 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("resize") + self.upcast::<EventTarget>() + .get_event_handler_common("load") } } - // https://html.spec.whatwg.org/multipage/#handler-onresize - fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) { + // 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().SetOnresize(listener); + document.window().SetOnload(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("resize", listener) + self.upcast::<EventTarget>() + .set_event_handler_common("load", listener) } } @@ -211,7 +208,8 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("blur") + self.upcast::<EventTarget>() + .get_event_handler_common("blur") } } @@ -223,7 +221,8 @@ impl HTMLElementMethods for HTMLElement { document.window().SetOnblur(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("blur", listener) + self.upcast::<EventTarget>() + .set_event_handler_common("blur", listener) } } @@ -237,7 +236,8 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("focus") + self.upcast::<EventTarget>() + .get_event_handler_common("focus") } } @@ -249,7 +249,36 @@ impl HTMLElementMethods for HTMLElement { document.window().SetOnfocus(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("focus", listener) + 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) } } @@ -263,7 +292,8 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("scroll") + self.upcast::<EventTarget>() + .get_event_handler_common("scroll") } } @@ -275,20 +305,61 @@ impl HTMLElementMethods for HTMLElement { document.window().SetOnscroll(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("scroll", listener) + 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) { - if !self.upcast::<Element>().disabled_state() { - synthetic_click_activation(self.upcast::<Element>(), - false, - false, - false, - false, - ActivationSource::FromClick) + 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 @@ -296,9 +367,7 @@ impl HTMLElementMethods for HTMLElement { // 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.begin_focus_transaction(); - document.request_focus(self.upcast()); - document.commit_focus_transaction(FocusType::Element); + document.request_focus(Some(self.upcast()), FocusType::Element); } // https://html.spec.whatwg.org/multipage/#dom-blur @@ -309,20 +378,18 @@ impl HTMLElementMethods for HTMLElement { } // https://html.spec.whatwg.org/multipage/#unfocusing-steps let document = document_from_node(self); - document.begin_focus_transaction(); - // If `request_focus` is not called, focus will be set to None. - document.commit_focus_transaction(FocusType::Element); + document.request_focus(None, FocusType::Element); } // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent - fn GetOffsetParent(&self) -> Option<Root<Element>> { + 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.to_trusted_node_address()); + let (element, _) = window.offset_parent_query(node); element } @@ -335,7 +402,7 @@ impl HTMLElementMethods for HTMLElement { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + let (_, rect) = window.offset_parent_query(node); rect.origin.y.to_nearest_px() } @@ -348,7 +415,7 @@ impl HTMLElementMethods for HTMLElement { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + let (_, rect) = window.offset_parent_query(node); rect.origin.x.to_nearest_px() } @@ -357,7 +424,7 @@ impl HTMLElementMethods for HTMLElement { fn OffsetWidth(&self) -> i32 { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + let (_, rect) = window.offset_parent_query(node); rect.size.width.to_nearest_px() } @@ -366,20 +433,144 @@ impl HTMLElementMethods for HTMLElement { fn OffsetHeight(&self) -> i32 { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + 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( + node.to_trusted_node_address(), + )); + DOMString::from(window.layout().element_inner_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); + 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( + // TODO change this to local_name! when html5ever updates + &LocalName::from("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 is_ascii_uppercase(c: char) -> bool { + 'A' <= c && c <= 'Z' +} + +fn is_ascii_lowercase(c: char) -> bool { + 'a' <= c && c <= 'w' +} + fn to_snake_case(name: DOMString) -> DOMString { - let mut attr_name = "data-".to_owned(); + 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_uppercase() { - attr_name.push('\x2d'); - attr_name.extend(ch.to_lowercase()); + if is_ascii_uppercase(ch) { + attr_name.push(DATA_HYPHEN_SEPARATOR); + attr_name.push(ch.to_ascii_lowercase()); } else { attr_name.push(ch); } @@ -387,30 +578,27 @@ fn to_snake_case(name: DOMString) -> DOMString { 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-") { + if !name.starts_with(DATA_PREFIX) { return None; } let name = &name[5..]; - let has_uppercase = name.chars().any(|curr_char| { - curr_char.is_ascii() && curr_char.is_uppercase() - }); + let has_uppercase = name.chars().any(|curr_char| is_ascii_uppercase(curr_char)); if has_uppercase { return None; } - let mut result = "".to_owned(); + 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 == '\x2d' { + if curr_char == DATA_HYPHEN_SEPARATOR { if let Some(next_char) = name_chars.next() { - if next_char.is_ascii() && next_char.is_lowercase() { + if is_ascii_lowercase(next_char) { result.push(next_char.to_ascii_uppercase()); } else { result.push(curr_char); @@ -428,44 +616,51 @@ fn to_camel_case(name: &str) -> Option<DOMString> { 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 >= 'a' && ch <= 'z') { + if name + .chars() + .skip_while(|&ch| ch != '\u{2d}') + .nth(1) + .map_or(false, |ch| ch >= 'a' && ch <= 'z') + { return Err(Error::Syntax); } - self.upcast::<Element>().set_custom_attribute(to_snake_case(name), value) + 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 - }) + 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); + self.upcast::<Element>() + .remove_attribute(&ns!(), &local_name); } // https://html.spec.whatwg.org/multipage/#category-label pub fn is_labelable_element(&self) -> bool { // Note: HTMLKeygenElement is omitted because Servo doesn't currently implement it match self.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => - match type_id { - HTMLElementTypeId::HTMLInputElement => - self.downcast::<HTMLInputElement>().unwrap().type_() != atom!("hidden"), - HTMLElementTypeId::HTMLButtonElement | - HTMLElementTypeId::HTMLMeterElement | - HTMLElementTypeId::HTMLOutputElement | - HTMLElementTypeId::HTMLProgressElement | - HTMLElementTypeId::HTMLSelectElement | - HTMLElementTypeId::HTMLTextAreaElement => true, - _ => false, + 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, } } @@ -479,72 +674,166 @@ impl HTMLElement { } 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, - _ => false, - }, + NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id { + HTMLElementTypeId::HTMLButtonElement | + HTMLElementTypeId::HTMLFieldSetElement | + HTMLElementTypeId::HTMLInputElement | + HTMLElementTypeId::HTMLObjectElement | + HTMLElementTypeId::HTMLOutputElement | + HTMLElementTypeId::HTMLSelectElement | + HTMLElementTypeId::HTMLTextAreaElement => true, + _ => false, + }, _ => 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() + 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 - pub fn labels(&self) -> Root<NodeList> { - debug_assert!(self.is_labelable_element()); + // 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 window = window_from_node(element); - - // Traverse ancestors for implicitly associated <label> elements - // https://html.spec.whatwg.org/multipage/#the-label-element:attr-label-for-4 - let ancestors = - self.upcast::<Node>() - .ancestors() - .filter_map(Root::downcast::<HTMLElement>) - // If we reach a labelable element, we have a guarantee no ancestors above it - // will be a label for this HTMLElement - .take_while(|elem| !elem.is_labelable_element()) - .filter_map(Root::downcast::<HTMLLabelElement>) - .filter(|elem| !elem.upcast::<Element>().has_attribute(&local_name!("for"))) - .filter(|elem| elem.first_labelable_descendant().r() == Some(self)) - .map(Root::upcast::<Node>); - - let id = element.Id(); - let id = match &id as &str { - "" => return NodeList::new_simple_list(&window, ancestors), - id => id, - }; - - // Traverse entire tree for <label> elements with `for` attribute matching `id` let root_element = element.root_element(); let root_node = root_element.upcast::<Node>(); - let children = root_node.traverse_preorder() - .filter_map(Root::downcast::<Element>) - .filter(|elem| elem.is::<HTMLLabelElement>()) - .filter(|elem| elem.get_string_attribute(&local_name!("for")) == id) - .map(Root::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(); - NodeList::new_simple_list(&window, children.chain(ancestors)) + // 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<&VirtualMethods> { - Some(self.upcast::<Element>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<Element>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -553,20 +842,41 @@ impl VirtualMethods for HTMLElement { (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())); + 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 bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + 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), } - self.update_sequentially_focusable_status(); + } +} + +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(); } } |