aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/htmlelement.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/htmlelement.rs')
-rw-r--r--components/script/dom/htmlelement.rs742
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();
}
}