/* 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/. */ //! Element nodes. use app_units::Au; use cssparser::Color; use devtools_traits::AttrInfo; use dom::activation::Activatable; use dom::attr::AttrValue; use dom::attr::{Attr, AttrHelpersForLayout}; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use dom::bindings::codegen::Bindings::ElementBinding; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::InheritTypes::{CharacterDataCast, DocumentDerived, ElementCast}; use dom::bindings::codegen::InheritTypes::{ElementDerived, ElementTypeId}; use dom::bindings::codegen::InheritTypes::{EventTargetCast, EventTargetTypeId}; use dom::bindings::codegen::InheritTypes::{HTMLAnchorElementCast, HTMLBodyElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLElementTypeId, HTMLFontElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLIFrameElementCast, HTMLInputElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLTableCellElementCast, HTMLTableElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLTableRowElementCast, HTMLTableSectionElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLTemplateElementCast, HTMLTextAreaElementCast}; use dom::bindings::codegen::InheritTypes::{NodeCast, NodeTypeId, TextCast}; use dom::bindings::codegen::UnionTypes::NodeOrString; use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::js::{JS, LayoutJS, MutNullableHeap}; use dom::bindings::js::{Root, RootedReference}; use dom::bindings::utils::XMLName::InvalidXMLName; use dom::bindings::utils::{namespace_from_domstring, validate_and_extract, xml_name_type}; use dom::create::create_element; use dom::document::{Document, LayoutDocumentHelpers}; use dom::domrect::DOMRect; use dom::domrectlist::DOMRectList; use dom::domtokenlist::DOMTokenList; use dom::event::Event; use dom::eventtarget::EventTarget; use dom::htmlcollection::HTMLCollection; use dom::htmlinputelement::{HTMLInputElement, RawLayoutHTMLInputElementHelpers}; use dom::htmltablecellelement::HTMLTableCellElementLayoutHelpers; use dom::htmltableelement::HTMLTableElement; use dom::htmltextareaelement::RawLayoutHTMLTextAreaElementHelpers; use dom::namednodemap::NamedNodeMap; use dom::node::{CLICK_IN_PROGRESS, LayoutNodeHelpers, Node, SEQUENTIALLY_FOCUSABLE}; use dom::node::{NodeDamage, document_from_node}; use dom::node::{window_from_node}; use dom::nodelist::NodeList; use dom::virtualmethods::{VirtualMethods, vtable_for}; use html5ever::serialize; use html5ever::serialize::SerializeOpts; use html5ever::serialize::TraversalScope; use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode}; use html5ever::tree_builder::{LimitedQuirks, NoQuirks, Quirks}; use selectors::matching::{DeclarationBlock, matches}; use selectors::parser::parse_author_origin_selector_list_from_str; use selectors::parser::{AttrSelector, NamespaceConstraint}; use smallvec::VecLike; use std::ascii::AsciiExt; use std::borrow::{Cow, ToOwned}; use std::cell::Ref; use std::default::Default; use std::mem; use std::sync::Arc; use string_cache::{Atom, Namespace, QualName}; use style::legacy::{UnsignedIntegerAttribute, from_declaration}; use style::properties::DeclaredValue; use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute}; use style::values::CSSFloat; use style::values::specified::{self, CSSColor, CSSRGBA}; use url::UrlParser; use util::str::{DOMString, LengthOrPercentageOrAuto}; #[dom_struct] pub struct Element { node: Node, local_name: Atom, namespace: Namespace, prefix: Option, attrs: DOMRefCell>>, id_attribute: DOMRefCell>, style_attribute: DOMRefCell>, attr_list: MutNullableHeap>, class_list: MutNullableHeap>, } impl ElementDerived for EventTarget { #[inline] fn is_element(&self) -> bool { match *self.type_id() { EventTargetTypeId::Node(NodeTypeId::Element(_)) => true, _ => false } } } impl PartialEq for Element { fn eq(&self, other: &Element) -> bool { self as *const Element == &*other } } #[derive(PartialEq, HeapSizeOf)] pub enum ElementCreator { ParserCreated, ScriptCreated, } // // Element methods // impl Element { pub fn create(name: QualName, prefix: Option, document: &Document, creator: ElementCreator) -> Root { create_element(name, prefix, document, creator) } pub fn new_inherited(type_id: ElementTypeId, local_name: DOMString, namespace: Namespace, prefix: Option, document: &Document) -> Element { Element { node: Node::new_inherited(NodeTypeId::Element(type_id), document), local_name: Atom::from_slice(&local_name), namespace: namespace, prefix: prefix, attrs: DOMRefCell::new(vec!()), attr_list: Default::default(), class_list: Default::default(), id_attribute: DOMRefCell::new(None), style_attribute: DOMRefCell::new(None), } } pub fn new(local_name: DOMString, namespace: Namespace, prefix: Option, document: &Document) -> Root { Node::reflect_node( box Element::new_inherited(ElementTypeId::Element, local_name, namespace, prefix, document), document, ElementBinding::Wrap) } } #[allow(unsafe_code)] pub trait RawLayoutElementHelpers { unsafe fn get_attr_for_layout<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a AttrValue>; unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a str>; unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &Atom) -> Vec<&'a str>; } #[inline] #[allow(unsafe_code)] pub unsafe fn get_attr_for_layout<'a>(elem: &'a Element, namespace: &Namespace, name: &Atom) -> Option> { // cast to point to T in RefCell directly let attrs = elem.attrs.borrow_for_layout(); attrs.iter().find(|attr: & &JS| { let attr = attr.to_layout(); *name == attr.local_name_atom_forever() && (*attr.unsafe_get()).namespace() == namespace }).map(|attr| attr.to_layout()) } #[allow(unsafe_code)] impl RawLayoutElementHelpers for Element { #[inline] unsafe fn get_attr_for_layout<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a AttrValue> { get_attr_for_layout(self, namespace, name).map(|attr| { attr.value_forever() }) } unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a str> { get_attr_for_layout(self, namespace, name).map(|attr| { attr.value_ref_forever() }) } #[inline] unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &Atom) -> Vec<&'a str> { let attrs = self.attrs.borrow_for_layout(); (*attrs).iter().filter_map(|attr: &JS| { let attr = attr.to_layout(); if *name == attr.local_name_atom_forever() { Some(attr.value_ref_forever()) } else { None } }).collect() } } pub trait LayoutElementHelpers { #[allow(unsafe_code)] unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &Atom) -> Option; #[allow(unsafe_code)] unsafe fn has_class_for_layout(&self, name: &Atom) -> bool; #[allow(unsafe_code)] unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]>; #[allow(unsafe_code)] unsafe fn synthesize_presentational_hints_for_legacy_attributes(&self, &mut V) where V: VecLike>>; #[allow(unsafe_code)] unsafe fn get_unsigned_integer_attribute_for_layout(&self, attribute: UnsignedIntegerAttribute) -> Option; #[allow(unsafe_code)] unsafe fn html_element_in_html_document_for_layout(&self) -> bool; #[allow(unsafe_code)] unsafe fn has_attr_for_layout(&self, namespace: &Namespace, name: &Atom) -> bool; fn id_attribute(&self) -> *const Option; fn style_attribute(&self) -> *const Option; fn local_name(&self) -> &Atom; fn namespace(&self) -> &Namespace; fn get_checked_state_for_layout(&self) -> bool; fn get_indeterminate_state_for_layout(&self) -> bool; } impl LayoutElementHelpers for LayoutJS { #[allow(unsafe_code)] #[inline] unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &Atom) -> Option { get_attr_for_layout(&*self.unsafe_get(), namespace, name).and_then(|attr| { attr.value_atom_forever() }) } #[allow(unsafe_code)] #[inline] unsafe fn has_class_for_layout(&self, name: &Atom) -> bool { get_attr_for_layout(&*self.unsafe_get(), &ns!(""), &atom!("class")).map_or(false, |attr| { attr.value_tokens_forever().unwrap().iter().any(|atom| atom == name) }) } #[allow(unsafe_code)] #[inline] unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]> { get_attr_for_layout(&*self.unsafe_get(), &ns!(""), &atom!("class")).map(|attr| { attr.value_tokens_forever().unwrap() }) } #[allow(unsafe_code)] unsafe fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) where V: VecLike>> { let bgcolor = if let Some(this) = HTMLBodyElementCast::to_layout_js(self) { (*this.unsafe_get()).get_background_color() } else if let Some(this) = HTMLTableElementCast::to_layout_js(self) { (*this.unsafe_get()).get_background_color() } else if let Some(this) = HTMLTableCellElementCast::to_layout_js(self) { this.get_background_color() } else if let Some(this) = HTMLTableRowElementCast::to_layout_js(self) { (*this.unsafe_get()).get_background_color() } else if let Some(this) = HTMLTableSectionElementCast::to_layout_js(self) { (*this.unsafe_get()).get_background_color() } else { None }; if let Some(color) = bgcolor { hints.push(from_declaration( PropertyDeclaration::BackgroundColor(DeclaredValue::Value( CSSColor { parsed: Color::RGBA(color), authored: None })))); } let background = if let Some(this) = HTMLBodyElementCast::to_layout_js(self) { (*this.unsafe_get()).get_background() } else { None }; if let Some(url) = background { hints.push(from_declaration( PropertyDeclaration::BackgroundImage(DeclaredValue::Value( background_image::SpecifiedValue(Some(specified::Image::Url(url))))))); } let color = if let Some(this) = HTMLFontElementCast::to_layout_js(self) { (*this.unsafe_get()).get_color() } else if let Some(this) = HTMLBodyElementCast::to_layout_js(self) { // https://html.spec.whatwg.org/multipage/#the-page:the-body-element-20 (*this.unsafe_get()).get_color() } else { None }; if let Some(color) = color { hints.push(from_declaration( PropertyDeclaration::Color(DeclaredValue::Value(CSSRGBA { parsed: color, authored: None, })))); } let font_family = if let Some(this) = HTMLFontElementCast::to_layout_js(self) { (*this.unsafe_get()).get_face() } else { None }; if let Some(font_family) = font_family { hints.push(from_declaration( PropertyDeclaration::FontFamily( DeclaredValue::Value( font_family::computed_value::T(vec![ font_family::computed_value::FontFamily::FamilyName( font_family)]))))); } let font_size = if let Some(this) = HTMLFontElementCast::to_layout_js(self) { (*this.unsafe_get()).get_size() } else { None }; if let Some(font_size) = font_size { hints.push(from_declaration( PropertyDeclaration::FontSize( DeclaredValue::Value( font_size::SpecifiedValue(font_size))))) } let cellspacing = if let Some(this) = HTMLTableElementCast::to_layout_js(self) { (*this.unsafe_get()).get_cellspacing() } else { None }; if let Some(cellspacing) = cellspacing { let width_value = specified::Length::Absolute(Au::from_px(cellspacing as i32)); hints.push(from_declaration( PropertyDeclaration::BorderSpacing(DeclaredValue::Value( border_spacing::SpecifiedValue { horizontal: width_value, vertical: width_value, })))); } let size = if let Some(this) = HTMLInputElementCast::to_layout_js(self) { // FIXME(pcwalton): More use of atoms, please! // FIXME(Ms2ger): this is nonsense! Invalid values also end up as // a text field match (*self.unsafe_get()).get_attr_val_for_layout(&ns!(""), &atom!("type")) { Some("text") | Some("password") => { match (*this.unsafe_get()).get_size_for_layout() { 0 => None, s => Some(s as i32), } } _ => None } } else { None }; if let Some(size) = size { let value = specified::Length::ServoCharacterWidth( specified::CharacterWidth(size)); hints.push(from_declaration( PropertyDeclaration::Width(DeclaredValue::Value( specified::LengthOrPercentageOrAuto::Length(value))))); } let width = if let Some(this) = HTMLIFrameElementCast::to_layout_js(self) { (*this.unsafe_get()).get_width() } else if let Some(this) = HTMLTableElementCast::to_layout_js(self) { (*this.unsafe_get()).get_width() } else if let Some(this) = HTMLTableCellElementCast::to_layout_js(self) { this.get_width() } else { LengthOrPercentageOrAuto::Auto }; match width { LengthOrPercentageOrAuto::Auto => {} LengthOrPercentageOrAuto::Percentage(percentage) => { let width_value = specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage)); hints.push(from_declaration( PropertyDeclaration::Width(DeclaredValue::Value(width_value)))); } LengthOrPercentageOrAuto::Length(length) => { let width_value = specified::LengthOrPercentageOrAuto::Length( specified::Length::Absolute(length)); hints.push(from_declaration( PropertyDeclaration::Width(DeclaredValue::Value(width_value)))); } } let height = if let Some(this) = HTMLIFrameElementCast::to_layout_js(self) { (*this.unsafe_get()).get_height() } else { LengthOrPercentageOrAuto::Auto }; match height { LengthOrPercentageOrAuto::Auto => {} LengthOrPercentageOrAuto::Percentage(percentage) => { let height_value = specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage)); hints.push(from_declaration( PropertyDeclaration::Height(DeclaredValue::Value(height_value)))); } LengthOrPercentageOrAuto::Length(length) => { let height_value = specified::LengthOrPercentageOrAuto::Length( specified::Length::Absolute(length)); hints.push(from_declaration( PropertyDeclaration::Height(DeclaredValue::Value(height_value)))); } } let cols = if let Some(this) = HTMLTextAreaElementCast::to_layout_js(self) { match (*this.unsafe_get()).get_cols_for_layout() { 0 => None, c => Some(c as i32), } } else { None }; if let Some(cols) = cols { // TODO(mttr) ServoCharacterWidth uses the size math for , but // the math for