/* 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::NodeBinding::NodeMethods; use dom::bindings::codegen::UnionTypes::NodeOrString; use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use dom::bindings::js::{JS, LayoutJS, MutNullableHeap}; use dom::bindings::js::{Root, RootedReference}; use dom::bindings::trace::JSTraceable; use dom::bindings::xmlname::XMLName::InvalidXMLName; use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type}; use dom::characterdata::CharacterData; 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::htmlanchorelement::HTMLAnchorElement; use dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers}; use dom::htmlcollection::HTMLCollection; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers}; use dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers}; use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; use dom::htmllabelelement::HTMLLabelElement; use dom::htmllegendelement::HTMLLegendElement; use dom::htmloptgroupelement::HTMLOptGroupElement; use dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementLayoutHelpers}; use dom::htmltableelement::{HTMLTableElement, HTMLTableElementLayoutHelpers}; use dom::htmltablerowelement::{HTMLTableRowElement, HTMLTableRowElementLayoutHelpers}; use dom::htmltablesectionelement::{HTMLTableSectionElement, HTMLTableSectionElementLayoutHelpers}; use dom::htmltemplateelement::HTMLTemplateElement; use dom::htmltextareaelement::{HTMLTextAreaElement, RawLayoutHTMLTextAreaElementHelpers}; use dom::namednodemap::NamedNodeMap; use dom::node::{CLICK_IN_PROGRESS, LayoutNodeHelpers, Node}; use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE}; use dom::node::{document_from_node, window_from_node}; use dom::nodelist::NodeList; use dom::text::Text; 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::matching::{common_style_affecting_attributes, rare_style_affecting_attributes}; use selectors::parser::{AttrSelector, NamespaceConstraint, parse_author_origin_selector_list_from_str}; use selectors::states::*; use smallvec::VecLike; use std::ascii::AsciiExt; use std::borrow::Cow; use std::cell::{Cell, Ref}; use std::default::Default; use std::mem; use std::sync::Arc; use string_cache::{Atom, Namespace, QualName}; 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, LengthOrPercentage}; use style_traits::ParseErrorReporter; use url::UrlParser; use util::mem::HeapSizeOf; use util::str::{DOMString, LengthOrPercentageOrAuto}; // TODO: Update focus state when the top-level browsing context gains or loses system focus, // and when the element enters or leaves a browsing context container. // https://html.spec.whatwg.org/multipage/#selector-focus #[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>, state: Cell, } 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(local_name: DOMString, namespace: Namespace, prefix: Option, document: &Document) -> Element { Element::new_inherited_with_state(ElementState::empty(), local_name, namespace, prefix, document) } pub fn new_inherited_with_state(state: ElementState, local_name: DOMString, namespace: Namespace, prefix: Option, document: &Document) -> Element { Element { node: Node::new_inherited(document), local_name: Atom::from(&*local_name), namespace: namespace, prefix: prefix, attrs: DOMRefCell::new(vec![]), id_attribute: DOMRefCell::new(None), style_attribute: DOMRefCell::new(None), attr_list: Default::default(), class_list: Default::default(), state: Cell::new(state), } } pub fn new(local_name: DOMString, namespace: Namespace, prefix: Option, document: &Document) -> Root { Node::reflect_node( box Element::new_inherited(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| { 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| { 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 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_colspan(self) -> u32; #[allow(unsafe_code)] unsafe fn html_element_in_html_document_for_layout(&self) -> 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; fn get_state_for_layout(&self) -> ElementState; } impl LayoutElementHelpers for LayoutJS { #[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>> { #[inline] fn from_declaration(rule: PropertyDeclaration) -> DeclarationBlock> { DeclarationBlock::from_declarations(Arc::new(vec![rule])) } let bgcolor = if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.get_background_color() } else if let Some(this) = self.downcast::() { this.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) = self.downcast::() { this.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) = self.downcast::() { this.get_color() } else if let Some(this) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#the-page:the-body-element-20 this.get_color() } else if let Some(this) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#the-hr-element-2:presentational-hints-5 this.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) = self.downcast::() { this.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) = self.downcast::() { this.get_size() } else { None }; if let Some(font_size) = font_size { hints.push(from_declaration( PropertyDeclaration::FontSize( DeclaredValue::Value( font_size::SpecifiedValue( LengthOrPercentage::Length(font_size)))))) } let cellspacing = if let Some(this) = self.downcast::() { 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(DeclaredValue::Value( border_spacing::SpecifiedValue { horizontal: width_value, vertical: width_value, })))); } let size = if let Some(this) = self.downcast::() { // 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.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) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { this.get_width() } else if let Some(this) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#the-hr-element-2:attr-hr-width 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) = self.downcast::() { this.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) = self.downcast::() { 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