/* 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 dom::activation::Activatable; use dom::attr::AttrValue; use dom::attr::{Attr, AttrSettingType, 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::NamedNodeMapBinding::NamedNodeMapMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::InheritTypes::CharacterDataCast; use dom::bindings::codegen::InheritTypes::DocumentDerived; use dom::bindings::codegen::InheritTypes::HTMLAnchorElementCast; use dom::bindings::codegen::InheritTypes::TextCast; use dom::bindings::codegen::InheritTypes::{ElementCast, ElementDerived, EventTargetCast}; use dom::bindings::codegen::InheritTypes::{HTMLBodyElementDerived, HTMLFontElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived, HTMLInputElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLTableElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLTableElementDerived, HTMLTableCellElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLTableRowElementDerived, HTMLTextAreaElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLTableSectionElementDerived, NodeCast}; use dom::bindings::codegen::UnionTypes::NodeOrString; use dom::bindings::error::Error::NoModificationAllowed; use dom::bindings::error::Error::{InvalidCharacter, Syntax}; use dom::bindings::error::{ErrorResult, Fallible}; use dom::bindings::js::{JS, LayoutJS, MutNullableHeap}; use dom::bindings::js::{Root, RootedReference}; use dom::bindings::trace::RootedVec; use dom::bindings::utils::XMLName::InvalidXMLName; use dom::bindings::utils::{namespace_from_domstring, xml_name_type, validate_and_extract}; 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, EventTargetTypeId}; use dom::htmlbodyelement::HTMLBodyElement; use dom::htmlcollection::HTMLCollection; use dom::htmlelement::HTMLElementTypeId; use dom::htmlfontelement::HTMLFontElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::htmlinputelement::{HTMLInputElement, RawLayoutHTMLInputElementHelpers}; use dom::htmltablecellelement::HTMLTableCellElement; use dom::htmltableelement::HTMLTableElement; use dom::htmltablerowelement::HTMLTableRowElement; use dom::htmltablesectionelement::HTMLTableSectionElement; use dom::htmltextareaelement::{HTMLTextAreaElement, RawLayoutHTMLTextAreaElementHelpers}; use dom::namednodemap::NamedNodeMap; use dom::node::{CLICK_IN_PROGRESS, LayoutNodeHelpers, Node, NodeTypeId, SEQUENTIALLY_FOCUSABLE}; use dom::node::{document_from_node, NodeDamage}; use dom::node::{window_from_node}; use dom::nodelist::NodeList; use dom::virtualmethods::{VirtualMethods, vtable_for}; use devtools_traits::AttrInfo; use smallvec::VecLike; use style::legacy::{UnsignedIntegerAttribute, from_declaration}; use style::properties::DeclaredValue::SpecifiedValue; use style::properties::longhands::{self, background_image, border_spacing}; use style::properties::{PropertyDeclarationBlock, PropertyDeclaration, parse_style_attribute}; use style::values::CSSFloat; use style::values::specified::{self, CSSColor, CSSRGBA}; use util::geometry::Au; use util::str::{DOMString, LengthOrPercentageOrAuto}; use cssparser::Color; use html5ever::serialize; use html5ever::serialize::SerializeOpts; use html5ever::serialize::TraversalScope; use html5ever::serialize::TraversalScope::{IncludeNode, ChildrenOnly}; use html5ever::tree_builder::{NoQuirks, LimitedQuirks, Quirks}; use selectors::matching::{matches, DeclarationBlock}; use selectors::parser::parse_author_origin_selector_list_from_str; use selectors::parser::{AttrSelector, NamespaceConstraint}; use string_cache::{Atom, Namespace, QualName}; use url::UrlParser; use std::ascii::AsciiExt; use std::borrow::{Cow, ToOwned}; use std::cell::{Ref, RefMut}; use std::default::Default; use std::mem; use std::sync::Arc; #[dom_struct] pub struct Element { node: Node, local_name: Atom, namespace: Namespace, prefix: Option, attrs: 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(JSTraceable, Copy, Clone, PartialEq, Debug, HeapSizeOf)] pub enum ElementTypeId { HTMLElement(HTMLElementTypeId), Element, } #[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(), 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>; unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &Atom) -> Option; unsafe fn has_class_for_layout(&self, name: &Atom) -> bool; unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]>; unsafe fn synthesize_presentational_hints_for_legacy_attributes(&self, &mut V) where V: VecLike>>; unsafe fn get_unsigned_integer_attribute_for_layout(&self, attribute: UnsignedIntegerAttribute) -> Option; } #[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() } #[inline] unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &Atom) -> Option { get_attr_for_layout(self, namespace, name).and_then(|attr| { attr.value_atom_forever() }) } #[inline] unsafe fn has_class_for_layout(&self, name: &Atom) -> bool { get_attr_for_layout(self, &ns!(""), &atom!("class")).map_or(false, |attr| { attr.value_tokens_forever().unwrap().iter().any(|atom| atom == name) }) } #[inline] unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]> { get_attr_for_layout(self, &ns!(""), &atom!("class")).map(|attr| { attr.value_tokens_forever().unwrap() }) } unsafe fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) where V: VecLike>> { let bgcolor = if self.is_htmlbodyelement() { let this: &HTMLBodyElement = mem::transmute(self); this.get_background_color() } else if self.is_htmltableelement() { let this: &HTMLTableElement = mem::transmute(self); this.get_background_color() } else if self.is_htmltablecellelement() { let this: &HTMLTableCellElement = mem::transmute(self); this.get_background_color() } else if self.is_htmltablerowelement() { let this: &HTMLTableRowElement = mem::transmute(self); this.get_background_color() } else if self.is_htmltablesectionelement() { let this: &HTMLTableSectionElement = mem::transmute(self); this.get_background_color() } else { None }; if let Some(color) = bgcolor { hints.push(from_declaration( PropertyDeclaration::BackgroundColor(SpecifiedValue( CSSColor { parsed: Color::RGBA(color), authored: None })))); } let background = if self.is_htmlbodyelement() { let this: &HTMLBodyElement = mem::transmute(self); this.get_background() } else { None }; if let Some(url) = background { hints.push(from_declaration( PropertyDeclaration::BackgroundImage(SpecifiedValue( background_image::SpecifiedValue(Some(specified::Image::Url(url))))))); } let color = if self.is_htmlfontelement() { let this: &HTMLFontElement = mem::transmute(self); this.get_color() } else { None }; if let Some(color) = color { hints.push(from_declaration( PropertyDeclaration::Color(SpecifiedValue(CSSRGBA { parsed: color, authored: None, })))); } let cellspacing = if self.is_htmltableelement() { let this: &HTMLTableElement = mem::transmute(self); this.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(SpecifiedValue( border_spacing::SpecifiedValue { horizontal: width_value, vertical: width_value, })))); } let size = if self.is_htmlinputelement() { // FIXME(pcwalton): More use of atoms, please! // FIXME(Ms2ger): this is nonsense! Invalid values also end up as // a text field match self.get_attr_val_for_layout(&ns!(""), &atom!("type")) { Some("text") | Some("password") => { let this: &HTMLInputElement = mem::transmute(self); match this.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(SpecifiedValue( specified::LengthOrPercentageOrAuto::Length(value))))); } let width = if self.is_htmliframeelement() { let this: &HTMLIFrameElement = mem::transmute(self); this.get_width() } else if self.is_htmltableelement() { let this: &HTMLTableElement = mem::transmute(self); this.get_width() } else if self.is_htmltablecellelement() { let this: &HTMLTableCellElement = mem::transmute(self); this.get_width() } else { LengthOrPercentageOrAuto::Auto }; match width { LengthOrPercentageOrAuto::Auto => {} LengthOrPercentageOrAuto::Percentage(percentage) => { let width_value = specified::LengthOrPercentageOrAuto::Percentage(percentage); hints.push(from_declaration( PropertyDeclaration::Width(SpecifiedValue(width_value)))); } LengthOrPercentageOrAuto::Length(length) => { let width_value = specified::LengthOrPercentageOrAuto::Length( specified::Length::Absolute(length)); hints.push(from_declaration( PropertyDeclaration::Width(SpecifiedValue(width_value)))); } } let height = if self.is_htmliframeelement() { let this: &HTMLIFrameElement = mem::transmute(self); this.get_height() } else { LengthOrPercentageOrAuto::Auto }; match height { LengthOrPercentageOrAuto::Auto => {} LengthOrPercentageOrAuto::Percentage(percentage) => { let height_value = specified::LengthOrPercentageOrAuto::Percentage(percentage); hints.push(from_declaration( PropertyDeclaration::Height(SpecifiedValue(height_value)))); } LengthOrPercentageOrAuto::Length(length) => { let height_value = specified::LengthOrPercentageOrAuto::Length( specified::Length::Absolute(length)); hints.push(from_declaration( PropertyDeclaration::Height(SpecifiedValue(height_value)))); } } let cols = if self.is_htmltextareaelement() { let this: &HTMLTextAreaElement = mem::transmute(self); match this.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