mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Now that JSRef<T> is gone, there is no need to have helper traits. On components/script/*.rs: # Remove imports. /^ *use dom::[a-z]+::\{.*Helpers/ { s/\{(Raw[^L]|[^L][^a])[a-zA-Z]+Helpers, /\{/ s/, (Raw[^L]|[^L][^a])[a-zA-Z]+Helpers([,}])/\2/g s/\{([a-zA-Z]+)\}/\1/ /\{\}/d s/::self;$/;/ } /^ *use dom::[a-z]+::\{?(Raw[^L]|[^L][^a])[a-zA-Z]+Helpers\}?;$/d On components/script/dom/*.rs: # Ignore layout things. /^(pub )?(impl|trait).*Layout.* \{/,/^}$/ { P; D; } # Delete helpers traits. /^(pub )?trait ([^L][^ ]|L[^a])[^ ]+Helpers(<'a>)? \{$/,/^\}$/D # Patch private helpers. /^impl.*Private.*Helpers/,/^\}$/ { s/^impl<'a> Private([^L][^ ]|L[^a])[^ ]+Helpers(<'a>)? for &'a ([^ ]+) \{$/impl \3 {/ /^ *(unsafe )?fn .*\(self.*[<&]'a/ { s/&'a /\&/g s/<'a, /</g } /^ *(unsafe )?fn /s/\(self([,)])/\(\&self\1/ } # Patch public helpers. /^impl.*Helpers/,/^\}$/ { s/^impl(<'a>)? ([^L][^ ]|L[^a])[^ ]+Helpers(<'a>)? for (&'a )?([^ ]+) \{$/impl \5 {/ /^ *(unsafe )?fn .*\(self.*[<&]'a/ { s/&'a /\&/g s/<'a, /</g } /^ *(unsafe )?fn .*\(&?self[,)]/s/(unsafe )?fn/pub &/ /^ *pub (unsafe )?fn /s/\(self([,)])/\(\&self\1/ } The few error cases were then fixed by hand.
1927 lines
72 KiB
Rust
1927 lines
72 KiB
Rust
/* 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<DOMString>,
|
|
attrs: DOMRefCell<Vec<JS<Attr>>>,
|
|
style_attribute: DOMRefCell<Option<PropertyDeclarationBlock>>,
|
|
attr_list: MutNullableHeap<JS<NamedNodeMap>>,
|
|
class_list: MutNullableHeap<JS<DOMTokenList>>,
|
|
}
|
|
|
|
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<Atom>,
|
|
document: &Document, creator: ElementCreator)
|
|
-> Root<Element> {
|
|
create_element(name, prefix, document, creator)
|
|
}
|
|
|
|
pub fn new_inherited(type_id: ElementTypeId, local_name: DOMString,
|
|
namespace: Namespace, prefix: Option<DOMString>,
|
|
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<DOMString>,
|
|
document: &Document) -> Root<Element> {
|
|
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<Atom>;
|
|
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<V>(&self, &mut V)
|
|
where V: VecLike<DeclarationBlock<Vec<PropertyDeclaration>>>;
|
|
unsafe fn get_unsigned_integer_attribute_for_layout(&self, attribute: UnsignedIntegerAttribute)
|
|
-> Option<u32>;
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
pub unsafe fn get_attr_for_layout<'a>(elem: &'a Element, namespace: &Namespace, name: &Atom)
|
|
-> Option<LayoutJS<Attr>> {
|
|
// cast to point to T in RefCell<T> directly
|
|
let attrs = elem.attrs.borrow_for_layout();
|
|
attrs.iter().find(|attr: & &JS<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: &JS<Attr>| {
|
|
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<Atom> {
|
|
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<V>(&self, hints: &mut V)
|
|
where V: VecLike<DeclarationBlock<Vec<PropertyDeclaration>>>
|
|
{
|
|
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 <input type="text">, but
|
|
// the math for <textarea> is a little different since we need to take
|
|
// scrollbar size into consideration (but we don't have a scrollbar yet!)
|
|
//
|
|
// https://html.spec.whatwg.org/multipage/#textarea-effective-width
|
|
let value = specified::Length::ServoCharacterWidth(specified::CharacterWidth(cols));
|
|
hints.push(from_declaration(
|
|
PropertyDeclaration::Width(SpecifiedValue(
|
|
specified::LengthOrPercentageOrAuto::Length(value)))));
|
|
}
|
|
|
|
|
|
let rows = if self.is_htmltextareaelement() {
|
|
let this: &HTMLTextAreaElement = mem::transmute(self);
|
|
match this.get_rows_for_layout() {
|
|
0 => None,
|
|
r => Some(r as i32),
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(rows) = rows {
|
|
// TODO(mttr) This should take scrollbar size into consideration.
|
|
//
|
|
// https://html.spec.whatwg.org/multipage/#textarea-effective-height
|
|
let value = specified::Length::FontRelative(specified::FontRelativeLength::Em(rows as CSSFloat));
|
|
hints.push(from_declaration(
|
|
PropertyDeclaration::Height(SpecifiedValue(
|
|
specified::LengthOrPercentageOrAuto::Length(value)))));
|
|
}
|
|
|
|
|
|
let border = if self.is_htmltableelement() {
|
|
let this: &HTMLTableElement = mem::transmute(self);
|
|
this.get_border()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(border) = border {
|
|
let width_value = specified::Length::Absolute(Au::from_px(border as i32));
|
|
hints.push(from_declaration(
|
|
PropertyDeclaration::BorderTopWidth(SpecifiedValue(
|
|
longhands::border_top_width::SpecifiedValue(width_value)))));
|
|
hints.push(from_declaration(
|
|
PropertyDeclaration::BorderLeftWidth(SpecifiedValue(
|
|
longhands::border_left_width::SpecifiedValue(width_value)))));
|
|
hints.push(from_declaration(
|
|
PropertyDeclaration::BorderBottomWidth(SpecifiedValue(
|
|
longhands::border_bottom_width::SpecifiedValue(width_value)))));
|
|
hints.push(from_declaration(
|
|
PropertyDeclaration::BorderRightWidth(SpecifiedValue(
|
|
longhands::border_right_width::SpecifiedValue(width_value)))));
|
|
}
|
|
}
|
|
|
|
unsafe fn get_unsigned_integer_attribute_for_layout(&self,
|
|
attribute: UnsignedIntegerAttribute)
|
|
-> Option<u32> {
|
|
match attribute {
|
|
UnsignedIntegerAttribute::ColSpan => {
|
|
if self.is_htmltablecellelement() {
|
|
let this: &HTMLTableCellElement = mem::transmute(self);
|
|
this.get_colspan()
|
|
} else {
|
|
// Don't panic since `display` can cause this to be called on arbitrary
|
|
// elements.
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait LayoutElementHelpers {
|
|
#[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 style_attribute(&self) -> *const Option<PropertyDeclarationBlock>;
|
|
fn local_name<'a>(&'a self) -> &'a Atom;
|
|
fn namespace<'a>(&'a self) -> &'a Namespace;
|
|
fn get_checked_state_for_layout(&self) -> bool;
|
|
fn get_indeterminate_state_for_layout(&self) -> bool;
|
|
}
|
|
|
|
impl LayoutElementHelpers for LayoutJS<Element> {
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
unsafe fn html_element_in_html_document_for_layout(&self) -> bool {
|
|
if (*self.unsafe_get()).namespace != ns!(HTML) {
|
|
return false
|
|
}
|
|
let node = NodeCast::from_layout_js(&self);
|
|
node.owner_doc_for_layout().is_html_document_for_layout()
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
unsafe fn has_attr_for_layout(&self, namespace: &Namespace, name: &Atom) -> bool {
|
|
get_attr_for_layout(&*self.unsafe_get(), namespace, name).is_some()
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn style_attribute(&self) -> *const Option<PropertyDeclarationBlock> {
|
|
unsafe {
|
|
(*self.unsafe_get()).style_attribute.borrow_for_layout()
|
|
}
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn local_name<'a>(&'a self) -> &'a Atom {
|
|
unsafe {
|
|
&(*self.unsafe_get()).local_name
|
|
}
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn namespace<'a>(&'a self) -> &'a Namespace {
|
|
unsafe {
|
|
&(*self.unsafe_get()).namespace
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
fn get_checked_state_for_layout(&self) -> bool {
|
|
// TODO option and menuitem can also have a checked state.
|
|
match HTMLInputElementCast::to_layout_js(self) {
|
|
Some(input) => unsafe {
|
|
(*input.unsafe_get()).get_checked_state_for_layout()
|
|
},
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(unsafe_code)]
|
|
fn get_indeterminate_state_for_layout(&self) -> bool {
|
|
// TODO progress elements can also be matched with :indeterminate
|
|
match HTMLInputElementCast::to_layout_js(self) {
|
|
Some(input) => unsafe {
|
|
(*input.unsafe_get()).get_indeterminate_state_for_layout()
|
|
},
|
|
None => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Copy, Clone, HeapSizeOf)]
|
|
pub enum StylePriority {
|
|
Important,
|
|
Normal,
|
|
}
|
|
|
|
|
|
impl Element {
|
|
pub fn html_element_in_html_document(&self) -> bool {
|
|
let node = NodeCast::from_ref(self);
|
|
self.namespace == ns!(HTML) && node.is_in_html_doc()
|
|
}
|
|
|
|
pub fn local_name(&self) -> &Atom {
|
|
&self.local_name
|
|
}
|
|
|
|
pub fn parsed_name(&self, name: DOMString) -> Atom {
|
|
if self.html_element_in_html_document() {
|
|
Atom::from_slice(&name.to_ascii_lowercase())
|
|
} else {
|
|
Atom::from_slice(&name)
|
|
}
|
|
}
|
|
|
|
pub fn namespace(&self) -> &Namespace {
|
|
&self.namespace
|
|
}
|
|
|
|
pub fn prefix(&self) -> &Option<DOMString> {
|
|
&self.prefix
|
|
}
|
|
|
|
pub fn attrs(&self) -> Ref<Vec<JS<Attr>>> {
|
|
self.attrs.borrow()
|
|
}
|
|
|
|
pub fn attrs_mut(&self) -> RefMut<Vec<JS<Attr>>> {
|
|
self.attrs.borrow_mut()
|
|
}
|
|
|
|
pub fn style_attribute(&self) -> &DOMRefCell<Option<PropertyDeclarationBlock>> {
|
|
&self.style_attribute
|
|
}
|
|
|
|
pub fn summarize(&self) -> Vec<AttrInfo> {
|
|
let attrs = self.Attributes();
|
|
let mut summarized = vec!();
|
|
for i in 0..attrs.r().Length() {
|
|
let attr = attrs.r().Item(i).unwrap();
|
|
summarized.push(attr.r().summarize());
|
|
}
|
|
summarized
|
|
}
|
|
|
|
pub fn is_void(&self) -> bool {
|
|
if self.namespace != ns!(HTML) {
|
|
return false
|
|
}
|
|
match &*self.local_name {
|
|
/* List of void elements from
|
|
https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm */
|
|
"area" | "base" | "basefont" | "bgsound" | "br" | "col" | "embed" |
|
|
"frame" | "hr" | "img" | "input" | "keygen" | "link" | "menuitem" |
|
|
"meta" | "param" | "source" | "track" | "wbr" => true,
|
|
_ => false
|
|
}
|
|
}
|
|
|
|
pub fn remove_inline_style_property(&self, property: &str) {
|
|
let mut inline_declarations = self.style_attribute.borrow_mut();
|
|
if let &mut Some(ref mut declarations) = &mut *inline_declarations {
|
|
let index = declarations.normal
|
|
.iter()
|
|
.position(|decl| decl.name() == property);
|
|
if let Some(index) = index {
|
|
Arc::make_unique(&mut declarations.normal).remove(index);
|
|
return;
|
|
}
|
|
|
|
let index = declarations.important
|
|
.iter()
|
|
.position(|decl| decl.name() == property);
|
|
if let Some(index) = index {
|
|
Arc::make_unique(&mut declarations.important).remove(index);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_inline_style(&self, property_decl: PropertyDeclaration, style_priority: StylePriority) {
|
|
let mut inline_declarations = self.style_attribute().borrow_mut();
|
|
if let &mut Some(ref mut declarations) = &mut *inline_declarations {
|
|
let existing_declarations = if style_priority == StylePriority::Important {
|
|
&mut declarations.important
|
|
} else {
|
|
&mut declarations.normal
|
|
};
|
|
|
|
// Usually, the reference count will be 1 here. But transitions could make it greater
|
|
// than that.
|
|
let existing_declarations = Arc::make_unique(existing_declarations);
|
|
for declaration in &mut *existing_declarations {
|
|
if declaration.name() == property_decl.name() {
|
|
*declaration = property_decl;
|
|
return;
|
|
}
|
|
}
|
|
existing_declarations.push(property_decl);
|
|
return;
|
|
}
|
|
|
|
let (important, normal) = if style_priority == StylePriority::Important {
|
|
(vec!(property_decl), vec!())
|
|
} else {
|
|
(vec!(), vec!(property_decl))
|
|
};
|
|
|
|
*inline_declarations = Some(PropertyDeclarationBlock {
|
|
important: Arc::new(important),
|
|
normal: Arc::new(normal),
|
|
});
|
|
}
|
|
|
|
pub fn set_inline_style_property_priority(&self, properties: &[&str], style_priority: StylePriority) {
|
|
let mut inline_declarations = self.style_attribute().borrow_mut();
|
|
if let &mut Some(ref mut declarations) = &mut *inline_declarations {
|
|
let (from, to) = if style_priority == StylePriority::Important {
|
|
(&mut declarations.normal, &mut declarations.important)
|
|
} else {
|
|
(&mut declarations.important, &mut declarations.normal)
|
|
};
|
|
|
|
// Usually, the reference counts of `from` and `to` will be 1 here. But transitions
|
|
// could make them greater than that.
|
|
let from = Arc::make_unique(from);
|
|
let to = Arc::make_unique(to);
|
|
let mut new_from = Vec::new();
|
|
for declaration in from.drain(..) {
|
|
if properties.contains(&declaration.name()) {
|
|
to.push(declaration)
|
|
} else {
|
|
new_from.push(declaration)
|
|
}
|
|
}
|
|
mem::replace(from, new_from);
|
|
}
|
|
}
|
|
|
|
pub fn get_inline_style_declaration(&self, property: &Atom) -> Option<Ref<PropertyDeclaration>> {
|
|
Ref::filter_map(self.style_attribute.borrow(), |inline_declarations| {
|
|
inline_declarations.as_ref().and_then(|declarations| {
|
|
declarations.normal
|
|
.iter()
|
|
.chain(declarations.important.iter())
|
|
.find(|decl| decl.matches(&property))
|
|
})
|
|
})
|
|
}
|
|
|
|
pub fn get_important_inline_style_declaration(&self, property: &Atom)
|
|
-> Option<Ref<PropertyDeclaration>> {
|
|
Ref::filter_map(self.style_attribute.borrow(), |inline_declarations| {
|
|
inline_declarations.as_ref().and_then(|declarations| {
|
|
declarations.important
|
|
.iter()
|
|
.find(|decl| decl.matches(&property))
|
|
})
|
|
})
|
|
}
|
|
|
|
pub fn serialize(&self, traversal_scope: TraversalScope) -> Fallible<DOMString> {
|
|
let node = NodeCast::from_ref(self);
|
|
let mut writer = vec![];
|
|
match serialize(&mut writer, &node,
|
|
SerializeOpts {
|
|
traversal_scope: traversal_scope,
|
|
.. Default::default()
|
|
}) {
|
|
Ok(()) => Ok(String::from_utf8(writer).unwrap()),
|
|
Err(_) => panic!("Cannot serialize element"),
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#root-element
|
|
pub fn get_root_element(&self) -> Root<Element> {
|
|
let node = NodeCast::from_ref(self);
|
|
node.inclusive_ancestors()
|
|
.filter_map(ElementCast::to_root)
|
|
.last()
|
|
.expect("We know inclusive_ancestors will return `self` which is an element")
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#locate-a-namespace-prefix
|
|
pub fn lookup_prefix(&self, namespace: Namespace) -> Option<DOMString> {
|
|
for node in NodeCast::from_ref(self).inclusive_ancestors() {
|
|
match ElementCast::to_ref(node.r()) {
|
|
Some(element) => {
|
|
// Step 1.
|
|
if *element.namespace() == namespace {
|
|
if let Some(prefix) = element.GetPrefix() {
|
|
return Some(prefix);
|
|
}
|
|
}
|
|
|
|
// Step 2.
|
|
let attrs = element.Attributes();
|
|
for i in 0..attrs.r().Length() {
|
|
let attr = attrs.r().Item(i).unwrap();
|
|
if *attr.r().prefix() == Some(atom!("xmlns")) &&
|
|
**attr.r().value() == *namespace.0 {
|
|
return Some(attr.r().LocalName());
|
|
}
|
|
}
|
|
},
|
|
None => return None,
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
|
|
impl Element {
|
|
pub fn is_focusable_area(&self) -> bool {
|
|
if self.is_actually_disabled() {
|
|
return false;
|
|
}
|
|
// TODO: Check whether the element is being rendered (i.e. not hidden).
|
|
let node = NodeCast::from_ref(self);
|
|
if node.get_flag(SEQUENTIALLY_FOCUSABLE) {
|
|
return true;
|
|
}
|
|
// https://html.spec.whatwg.org/multipage/#specially-focusable
|
|
match node.type_id() {
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => {
|
|
true
|
|
}
|
|
_ => false
|
|
}
|
|
}
|
|
|
|
pub fn is_actually_disabled(&self) -> bool {
|
|
let node = NodeCast::from_ref(self);
|
|
match node.type_id() {
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement)) => {
|
|
node.get_disabled_state()
|
|
}
|
|
// TODO:
|
|
// an optgroup element that has a disabled attribute
|
|
// a menuitem element that has a disabled attribute
|
|
// a fieldset element that is a disabled fieldset
|
|
_ => false
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait AttributeHandlers {
|
|
/// Returns the attribute with given namespace and case-sensitive local
|
|
/// name, if any.
|
|
fn get_attribute(self, namespace: &Namespace, local_name: &Atom)
|
|
-> Option<Root<Attr>>;
|
|
/// Returns the first attribute with any namespace and given case-sensitive
|
|
/// name, if any.
|
|
fn get_attribute_by_name(self, name: DOMString) -> Option<Root<Attr>>;
|
|
fn get_attributes(self, local_name: &Atom, attributes: &mut RootedVec<JS<Attr>>);
|
|
fn set_attribute_from_parser(self,
|
|
name: QualName,
|
|
value: DOMString,
|
|
prefix: Option<Atom>);
|
|
fn set_attribute(self, name: &Atom, value: AttrValue);
|
|
fn set_custom_attribute(self, name: DOMString, value: DOMString) -> ErrorResult;
|
|
fn do_set_attribute<F>(self, local_name: Atom, value: AttrValue,
|
|
name: Atom, namespace: Namespace,
|
|
prefix: Option<Atom>, cb: F)
|
|
where F: Fn(&Attr) -> bool;
|
|
fn parse_attribute(self, namespace: &Namespace, local_name: &Atom,
|
|
value: DOMString) -> AttrValue;
|
|
|
|
/// Removes the first attribute with any given namespace and case-sensitive local
|
|
/// name, if any.
|
|
fn remove_attribute(self, namespace: &Namespace, local_name: &Atom)
|
|
-> Option<Root<Attr>>;
|
|
/// Removes the first attribute with any namespace and given case-sensitive name.
|
|
fn remove_attribute_by_name(self, name: &Atom) -> Option<Root<Attr>>;
|
|
/// Removes the first attribute that satisfies `find`.
|
|
fn do_remove_attribute<F>(self, find: F) -> Option<Root<Attr>>
|
|
where F: Fn(&Attr) -> bool;
|
|
|
|
fn has_class(self, name: &Atom) -> bool;
|
|
|
|
fn set_atomic_attribute(self, local_name: &Atom, value: DOMString);
|
|
|
|
// https://www.whatwg.org/html/#reflecting-content-attributes-in-idl-attributes
|
|
fn has_attribute(self, local_name: &Atom) -> bool;
|
|
fn set_bool_attribute(self, local_name: &Atom, value: bool);
|
|
fn get_url_attribute(self, local_name: &Atom) -> DOMString;
|
|
fn set_url_attribute(self, local_name: &Atom, value: DOMString);
|
|
fn get_string_attribute(self, local_name: &Atom) -> DOMString;
|
|
fn set_string_attribute(self, local_name: &Atom, value: DOMString);
|
|
fn get_tokenlist_attribute(self, local_name: &Atom) -> Vec<Atom>;
|
|
fn set_tokenlist_attribute(self, local_name: &Atom, value: DOMString);
|
|
fn set_atomic_tokenlist_attribute(self, local_name: &Atom, tokens: Vec<Atom>);
|
|
fn get_uint_attribute(self, local_name: &Atom, default: u32) -> u32;
|
|
fn set_uint_attribute(self, local_name: &Atom, value: u32);
|
|
}
|
|
|
|
impl<'a> AttributeHandlers for &'a Element {
|
|
fn get_attribute(self, namespace: &Namespace, local_name: &Atom) -> Option<Root<Attr>> {
|
|
let mut attributes = RootedVec::new();
|
|
self.get_attributes(local_name, &mut attributes);
|
|
attributes.r().iter()
|
|
.find(|attr| attr.namespace() == namespace)
|
|
.map(|attr| Root::from_ref(*attr))
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
|
|
fn get_attribute_by_name(self, name: DOMString) -> Option<Root<Attr>> {
|
|
let name = &self.parsed_name(name);
|
|
self.attrs.borrow().iter().map(|attr| attr.root())
|
|
.find(|a| a.r().name() == name)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
|
|
fn get_attributes(self, local_name: &Atom, attributes: &mut RootedVec<JS<Attr>>) {
|
|
for ref attr in self.attrs.borrow().iter() {
|
|
let attr = attr.root();
|
|
if attr.r().local_name() == local_name {
|
|
attributes.push(JS::from_rooted(&attr));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_attribute_from_parser(self,
|
|
qname: QualName,
|
|
value: DOMString,
|
|
prefix: Option<Atom>) {
|
|
// Don't set if the attribute already exists, so we can handle add_attrs_if_missing
|
|
if self.attrs.borrow().iter().map(|attr| attr.root())
|
|
.any(|a| *a.r().local_name() == qname.local && *a.r().namespace() == qname.ns) {
|
|
return;
|
|
}
|
|
|
|
let name = match prefix {
|
|
None => qname.local.clone(),
|
|
Some(ref prefix) => {
|
|
let name = format!("{}:{}", &**prefix, &*qname.local);
|
|
Atom::from_slice(&name)
|
|
},
|
|
};
|
|
let value = self.parse_attribute(&qname.ns, &qname.local, value);
|
|
self.do_set_attribute(qname.local, value, name, qname.ns, prefix, |_| false)
|
|
}
|
|
|
|
fn set_attribute(self, name: &Atom, value: AttrValue) {
|
|
assert!(&**name == name.to_ascii_lowercase());
|
|
assert!(!name.contains(":"));
|
|
|
|
self.do_set_attribute(name.clone(), value, name.clone(),
|
|
ns!(""), None, |attr| attr.local_name() == name);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-data-*
|
|
fn set_custom_attribute(self, name: DOMString, value: DOMString) -> ErrorResult {
|
|
// Step 1.
|
|
match xml_name_type(&name) {
|
|
InvalidXMLName => return Err(InvalidCharacter),
|
|
_ => {}
|
|
}
|
|
|
|
// Steps 2-5.
|
|
let name = Atom::from_slice(&name);
|
|
let value = self.parse_attribute(&ns!(""), &name, value);
|
|
self.do_set_attribute(name.clone(), value, name.clone(), ns!(""), None, |attr| {
|
|
*attr.name() == name && *attr.namespace() == ns!("")
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
fn do_set_attribute<F>(self,
|
|
local_name: Atom,
|
|
value: AttrValue,
|
|
name: Atom,
|
|
namespace: Namespace,
|
|
prefix: Option<Atom>,
|
|
cb: F)
|
|
where F: Fn(&Attr) -> bool
|
|
{
|
|
let idx = self.attrs.borrow().iter()
|
|
.map(|attr| attr.root())
|
|
.position(|attr| cb(attr.r()));
|
|
let (idx, set_type) = match idx {
|
|
Some(idx) => (idx, AttrSettingType::ReplacedAttr),
|
|
None => {
|
|
let window = window_from_node(self);
|
|
let attr = Attr::new(window.r(), local_name, value.clone(),
|
|
name, namespace.clone(), prefix, Some(self));
|
|
self.attrs.borrow_mut().push(JS::from_rooted(&attr));
|
|
(self.attrs.borrow().len() - 1, AttrSettingType::FirstSetAttr)
|
|
}
|
|
};
|
|
|
|
(*self.attrs.borrow())[idx].root().r().set_value(set_type, value, self);
|
|
}
|
|
|
|
fn parse_attribute(self, namespace: &Namespace, local_name: &Atom,
|
|
value: DOMString) -> AttrValue {
|
|
if *namespace == ns!("") {
|
|
vtable_for(&NodeCast::from_ref(self))
|
|
.parse_plain_attribute(local_name, value)
|
|
} else {
|
|
AttrValue::String(value)
|
|
}
|
|
}
|
|
|
|
fn remove_attribute(self, namespace: &Namespace, local_name: &Atom)
|
|
-> Option<Root<Attr>> {
|
|
self.do_remove_attribute(|attr| {
|
|
attr.namespace() == namespace && attr.local_name() == local_name
|
|
})
|
|
}
|
|
|
|
fn remove_attribute_by_name(self, name: &Atom) -> Option<Root<Attr>> {
|
|
self.do_remove_attribute(|attr| attr.name() == name)
|
|
}
|
|
|
|
fn do_remove_attribute<F>(self, find: F) -> Option<Root<Attr>>
|
|
where F: Fn(&Attr) -> bool
|
|
{
|
|
let idx = self.attrs.borrow().iter()
|
|
.map(|attr| attr.root())
|
|
.position(|attr| find(attr.r()));
|
|
|
|
idx.map(|idx| {
|
|
let attr = (*self.attrs.borrow())[idx].root();
|
|
if attr.r().namespace() == &ns!("") {
|
|
vtable_for(&NodeCast::from_ref(self)).before_remove_attr(attr.r());
|
|
}
|
|
|
|
self.attrs.borrow_mut().remove(idx);
|
|
attr.r().set_owner(None);
|
|
if attr.r().namespace() == &ns!("") {
|
|
vtable_for(&NodeCast::from_ref(self)).after_remove_attr(attr.r().name());
|
|
}
|
|
|
|
let node = NodeCast::from_ref(self);
|
|
if node.is_in_doc() {
|
|
let document = document_from_node(self);
|
|
let damage = if attr.r().local_name() == &atom!("style") {
|
|
NodeDamage::NodeStyleDamaged
|
|
} else {
|
|
NodeDamage::OtherNodeDamage
|
|
};
|
|
document.r().content_changed(node, damage);
|
|
}
|
|
attr
|
|
})
|
|
}
|
|
|
|
fn has_class(self, name: &Atom) -> bool {
|
|
let quirks_mode = {
|
|
let node = NodeCast::from_ref(self);
|
|
let owner_doc = node.owner_doc();
|
|
owner_doc.r().quirks_mode()
|
|
};
|
|
let is_equal = |lhs: &Atom, rhs: &Atom| match quirks_mode {
|
|
NoQuirks | LimitedQuirks => lhs == rhs,
|
|
Quirks => lhs.eq_ignore_ascii_case(&rhs)
|
|
};
|
|
self.get_attribute(&ns!(""), &atom!("class")).map(|attr| {
|
|
attr.r().value().tokens().map(|tokens| {
|
|
tokens.iter().any(|atom| is_equal(name, atom))
|
|
}).unwrap_or(false)
|
|
}).unwrap_or(false)
|
|
}
|
|
|
|
fn set_atomic_attribute(self, local_name: &Atom, value: DOMString) {
|
|
assert!(&**local_name == local_name.to_ascii_lowercase());
|
|
let value = AttrValue::from_atomic(value);
|
|
self.set_attribute(local_name, value);
|
|
}
|
|
|
|
fn has_attribute(self, local_name: &Atom) -> bool {
|
|
assert!(local_name.bytes().all(|b| b.to_ascii_lowercase() == b));
|
|
self.attrs.borrow().iter().map(|attr| attr.root()).any(|attr| {
|
|
attr.r().local_name() == local_name && attr.r().namespace() == &ns!("")
|
|
})
|
|
}
|
|
|
|
fn set_bool_attribute(self, local_name: &Atom, value: bool) {
|
|
if self.has_attribute(local_name) == value { return; }
|
|
if value {
|
|
self.set_string_attribute(local_name, String::new());
|
|
} else {
|
|
self.remove_attribute(&ns!(""), local_name);
|
|
}
|
|
}
|
|
|
|
fn get_url_attribute(self, local_name: &Atom) -> DOMString {
|
|
assert!(&**local_name == local_name.to_ascii_lowercase());
|
|
if !self.has_attribute(local_name) {
|
|
return "".to_owned();
|
|
}
|
|
let url = self.get_string_attribute(local_name);
|
|
let doc = document_from_node(self);
|
|
let base = doc.r().url();
|
|
// https://html.spec.whatwg.org/multipage/#reflect
|
|
// XXXManishearth this doesn't handle `javascript:` urls properly
|
|
match UrlParser::new().base_url(&base).parse(&url) {
|
|
Ok(parsed) => parsed.serialize(),
|
|
Err(_) => "".to_owned()
|
|
}
|
|
}
|
|
fn set_url_attribute(self, local_name: &Atom, value: DOMString) {
|
|
self.set_string_attribute(local_name, value);
|
|
}
|
|
|
|
fn get_string_attribute(self, local_name: &Atom) -> DOMString {
|
|
match self.get_attribute(&ns!(""), local_name) {
|
|
Some(x) => x.r().Value(),
|
|
None => "".to_owned()
|
|
}
|
|
}
|
|
fn set_string_attribute(self, local_name: &Atom, value: DOMString) {
|
|
assert!(&**local_name == local_name.to_ascii_lowercase());
|
|
self.set_attribute(local_name, AttrValue::String(value));
|
|
}
|
|
|
|
fn get_tokenlist_attribute(self, local_name: &Atom) -> Vec<Atom> {
|
|
self.get_attribute(&ns!(""), local_name).map(|attr| {
|
|
attr.r()
|
|
.value()
|
|
.tokens()
|
|
.expect("Expected a TokenListAttrValue")
|
|
.to_vec()
|
|
}).unwrap_or(vec!())
|
|
}
|
|
|
|
fn set_tokenlist_attribute(self, local_name: &Atom, value: DOMString) {
|
|
assert!(&**local_name == local_name.to_ascii_lowercase());
|
|
self.set_attribute(local_name, AttrValue::from_serialized_tokenlist(value));
|
|
}
|
|
|
|
fn set_atomic_tokenlist_attribute(self, local_name: &Atom, tokens: Vec<Atom>) {
|
|
assert!(&**local_name == local_name.to_ascii_lowercase());
|
|
self.set_attribute(local_name, AttrValue::from_atomic_tokens(tokens));
|
|
}
|
|
|
|
fn get_uint_attribute(self, local_name: &Atom, default: u32) -> u32 {
|
|
assert!(local_name.chars().all(|ch| {
|
|
!ch.is_ascii() || ch.to_ascii_lowercase() == ch
|
|
}));
|
|
let attribute = self.get_attribute(&ns!(""), local_name);
|
|
match attribute {
|
|
Some(ref attribute) => {
|
|
match *attribute.r().value() {
|
|
AttrValue::UInt(_, value) => value,
|
|
_ => panic!("Expected an AttrValue::UInt: \
|
|
implement parse_plain_attribute"),
|
|
}
|
|
}
|
|
None => default,
|
|
}
|
|
}
|
|
fn set_uint_attribute(self, local_name: &Atom, value: u32) {
|
|
assert!(&**local_name == local_name.to_ascii_lowercase());
|
|
self.set_attribute(local_name, AttrValue::UInt(value.to_string(), value));
|
|
}
|
|
}
|
|
|
|
impl<'a> ElementMethods for &'a Element {
|
|
// https://dom.spec.whatwg.org/#dom-element-namespaceuri
|
|
fn GetNamespaceURI(self) -> Option<DOMString> {
|
|
Node::namespace_to_string(self.namespace.clone())
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-localname
|
|
fn LocalName(self) -> DOMString {
|
|
(*self.local_name).to_owned()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-prefix
|
|
fn GetPrefix(self) -> Option<DOMString> {
|
|
self.prefix.clone()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-tagname
|
|
fn TagName(self) -> DOMString {
|
|
let qualified_name = match self.prefix {
|
|
Some(ref prefix) => {
|
|
Cow::Owned(format!("{}:{}", &**prefix, &*self.local_name))
|
|
},
|
|
None => Cow::Borrowed(&*self.local_name)
|
|
};
|
|
if self.html_element_in_html_document() {
|
|
qualified_name.to_ascii_uppercase()
|
|
} else {
|
|
qualified_name.into_owned()
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-id
|
|
fn Id(self) -> DOMString {
|
|
self.get_string_attribute(&atom!("id"))
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-id
|
|
fn SetId(self, id: DOMString) {
|
|
self.set_atomic_attribute(&atom!("id"), id);
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-classname
|
|
fn ClassName(self) -> DOMString {
|
|
self.get_string_attribute(&atom!("class"))
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-classname
|
|
fn SetClassName(self, class: DOMString) {
|
|
self.set_tokenlist_attribute(&atom!("class"), class);
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-classlist
|
|
fn ClassList(self) -> Root<DOMTokenList> {
|
|
self.class_list.or_init(|| DOMTokenList::new(self, &atom!("class")))
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-attributes
|
|
fn Attributes(self) -> Root<NamedNodeMap> {
|
|
self.attr_list.or_init(|| {
|
|
let doc = {
|
|
let node = NodeCast::from_ref(self);
|
|
node.owner_doc()
|
|
};
|
|
let window = doc.r().window();
|
|
NamedNodeMap::new(window.r(), self)
|
|
})
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-getattribute
|
|
fn GetAttribute(self, name: DOMString) -> Option<DOMString> {
|
|
self.get_attribute_by_name(name)
|
|
.map(|s| s.r().Value())
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-getattributens
|
|
fn GetAttributeNS(self,
|
|
namespace: Option<DOMString>,
|
|
local_name: DOMString) -> Option<DOMString> {
|
|
let namespace = &namespace_from_domstring(namespace);
|
|
self.get_attribute(namespace, &Atom::from_slice(&local_name))
|
|
.map(|attr| attr.r().Value())
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-setattribute
|
|
fn SetAttribute(self,
|
|
name: DOMString,
|
|
value: DOMString) -> ErrorResult {
|
|
// Step 1.
|
|
if xml_name_type(&name) == InvalidXMLName {
|
|
return Err(InvalidCharacter);
|
|
}
|
|
|
|
// Step 2.
|
|
let name = self.parsed_name(name);
|
|
|
|
// Step 3-5.
|
|
let value = self.parse_attribute(&ns!(""), &name, value);
|
|
self.do_set_attribute(name.clone(), value, name.clone(), ns!(""), None, |attr| {
|
|
*attr.name() == name
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-setattributens
|
|
fn SetAttributeNS(self,
|
|
namespace: Option<DOMString>,
|
|
qualified_name: DOMString,
|
|
value: DOMString) -> ErrorResult {
|
|
let (namespace, prefix, local_name) =
|
|
try!(validate_and_extract(namespace, &qualified_name));
|
|
let qualified_name = Atom::from_slice(&qualified_name);
|
|
let value = self.parse_attribute(&namespace, &local_name, value);
|
|
self.do_set_attribute(local_name.clone(), value, qualified_name,
|
|
namespace.clone(), prefix, |attr| {
|
|
*attr.local_name() == local_name &&
|
|
*attr.namespace() == namespace
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-removeattribute
|
|
fn RemoveAttribute(self, name: DOMString) {
|
|
let name = self.parsed_name(name);
|
|
self.remove_attribute_by_name(&name);
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-removeattributens
|
|
fn RemoveAttributeNS(self,
|
|
namespace: Option<DOMString>,
|
|
local_name: DOMString) {
|
|
let namespace = namespace_from_domstring(namespace);
|
|
let local_name = Atom::from_slice(&local_name);
|
|
self.remove_attribute(&namespace, &local_name);
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-hasattribute
|
|
fn HasAttribute(self, name: DOMString) -> bool {
|
|
self.GetAttribute(name).is_some()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-hasattributens
|
|
fn HasAttributeNS(self,
|
|
namespace: Option<DOMString>,
|
|
local_name: DOMString) -> bool {
|
|
self.GetAttributeNS(namespace, local_name).is_some()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-getelementsbytagname
|
|
fn GetElementsByTagName(self, localname: DOMString) -> Root<HTMLCollection> {
|
|
let window = window_from_node(self);
|
|
HTMLCollection::by_tag_name(window.r(), NodeCast::from_ref(self), localname)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-getelementsbytagnamens
|
|
fn GetElementsByTagNameNS(self, maybe_ns: Option<DOMString>,
|
|
localname: DOMString) -> Root<HTMLCollection> {
|
|
let window = window_from_node(self);
|
|
HTMLCollection::by_tag_name_ns(window.r(), NodeCast::from_ref(self), localname, maybe_ns)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-getelementsbyclassname
|
|
fn GetElementsByClassName(self, classes: DOMString) -> Root<HTMLCollection> {
|
|
let window = window_from_node(self);
|
|
HTMLCollection::by_class_name(window.r(), NodeCast::from_ref(self), classes)
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-getclientrects
|
|
fn GetClientRects(self) -> Root<DOMRectList> {
|
|
let win = window_from_node(self);
|
|
let node = NodeCast::from_ref(self);
|
|
let raw_rects = node.get_content_boxes();
|
|
let rects = raw_rects.iter().map(|rect| {
|
|
DOMRect::new(win.r(),
|
|
rect.origin.y, rect.origin.y + rect.size.height,
|
|
rect.origin.x, rect.origin.x + rect.size.width)
|
|
});
|
|
DOMRectList::new(win.r(), rects)
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect
|
|
fn GetBoundingClientRect(self) -> Root<DOMRect> {
|
|
let win = window_from_node(self);
|
|
let node = NodeCast::from_ref(self);
|
|
let rect = node.get_bounding_content_box();
|
|
DOMRect::new(
|
|
win.r(),
|
|
rect.origin.y,
|
|
rect.origin.y + rect.size.height,
|
|
rect.origin.x,
|
|
rect.origin.x + rect.size.width)
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-clienttop
|
|
fn ClientTop(self) -> i32 {
|
|
let node = NodeCast::from_ref(self);
|
|
node.get_client_rect().origin.y
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-clientleft
|
|
fn ClientLeft(self) -> i32 {
|
|
let node = NodeCast::from_ref(self);
|
|
node.get_client_rect().origin.x
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-clientwidth
|
|
fn ClientWidth(self) -> i32 {
|
|
let node = NodeCast::from_ref(self);
|
|
node.get_client_rect().size.width
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom-view/#dom-element-clientheight
|
|
fn ClientHeight(self) -> i32 {
|
|
let node = NodeCast::from_ref(self);
|
|
node.get_client_rect().size.height
|
|
}
|
|
|
|
// https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-innerHTML
|
|
fn GetInnerHTML(self) -> Fallible<DOMString> {
|
|
//XXX TODO: XML case
|
|
self.serialize(ChildrenOnly)
|
|
}
|
|
|
|
// https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-innerHTML
|
|
fn SetInnerHTML(self, value: DOMString) -> Fallible<()> {
|
|
let context_node = NodeCast::from_ref(self);
|
|
// Step 1.
|
|
let frag = try!(context_node.parse_fragment(value));
|
|
// Step 2.
|
|
Node::replace_all(Some(NodeCast::from_ref(frag.r())), context_node);
|
|
Ok(())
|
|
}
|
|
|
|
// https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-outerHTML
|
|
fn GetOuterHTML(self) -> Fallible<DOMString> {
|
|
self.serialize(IncludeNode)
|
|
}
|
|
|
|
// https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-outerHTML
|
|
fn SetOuterHTML(self, value: DOMString) -> Fallible<()> {
|
|
let context_document = document_from_node(self);
|
|
let context_node = NodeCast::from_ref(self);
|
|
// Step 1.
|
|
let context_parent = match context_node.GetParentNode() {
|
|
None => {
|
|
// Step 2.
|
|
return Ok(());
|
|
},
|
|
Some(parent) => parent,
|
|
};
|
|
|
|
let parent = match context_parent.r().type_id() {
|
|
// Step 3.
|
|
NodeTypeId::Document => return Err(NoModificationAllowed),
|
|
|
|
// Step 4.
|
|
NodeTypeId::DocumentFragment => {
|
|
let body_elem = Element::create(QualName::new(ns!(HTML), atom!(body)),
|
|
None, context_document.r(),
|
|
ElementCreator::ScriptCreated);
|
|
NodeCast::from_root(body_elem)
|
|
},
|
|
_ => context_node.GetParentNode().unwrap()
|
|
};
|
|
|
|
// Step 5.
|
|
let frag = try!(parent.r().parse_fragment(value));
|
|
// Step 6.
|
|
try!(context_parent.r().ReplaceChild(NodeCast::from_ref(frag.r()),
|
|
context_node));
|
|
Ok(())
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling
|
|
fn GetPreviousElementSibling(self) -> Option<Root<Element>> {
|
|
NodeCast::from_ref(self).preceding_siblings()
|
|
.filter_map(ElementCast::to_root).next()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling
|
|
fn GetNextElementSibling(self) -> Option<Root<Element>> {
|
|
NodeCast::from_ref(self).following_siblings()
|
|
.filter_map(ElementCast::to_root).next()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-children
|
|
fn Children(self) -> Root<HTMLCollection> {
|
|
let window = window_from_node(self);
|
|
HTMLCollection::children(window.r(), NodeCast::from_ref(self))
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild
|
|
fn GetFirstElementChild(self) -> Option<Root<Element>> {
|
|
NodeCast::from_ref(self).child_elements().next()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild
|
|
fn GetLastElementChild(self) -> Option<Root<Element>> {
|
|
NodeCast::from_ref(self).rev_children().filter_map(ElementCast::to_root).next()
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-childelementcount
|
|
fn ChildElementCount(self) -> u32 {
|
|
NodeCast::from_ref(self).child_elements().count() as u32
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-prepend
|
|
fn Prepend(self, nodes: Vec<NodeOrString>) -> ErrorResult {
|
|
NodeCast::from_ref(self).prepend(nodes)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-append
|
|
fn Append(self, nodes: Vec<NodeOrString>) -> ErrorResult {
|
|
NodeCast::from_ref(self).append(nodes)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-queryselector
|
|
fn QuerySelector(self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
|
|
let root = NodeCast::from_ref(self);
|
|
root.query_selector(selectors)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
|
|
fn QuerySelectorAll(self, selectors: DOMString) -> Fallible<Root<NodeList>> {
|
|
let root = NodeCast::from_ref(self);
|
|
root.query_selector_all(selectors)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-childnode-before
|
|
fn Before(self, nodes: Vec<NodeOrString>) -> ErrorResult {
|
|
NodeCast::from_ref(self).before(nodes)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-childnode-after
|
|
fn After(self, nodes: Vec<NodeOrString>) -> ErrorResult {
|
|
NodeCast::from_ref(self).after(nodes)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-childnode-replacewith
|
|
fn ReplaceWith(self, nodes: Vec<NodeOrString>) -> ErrorResult {
|
|
NodeCast::from_ref(self).replace_with(nodes)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-childnode-remove
|
|
fn Remove(self) {
|
|
let node = NodeCast::from_ref(self);
|
|
node.remove_self();
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-matches
|
|
fn Matches(self, selectors: DOMString) -> Fallible<bool> {
|
|
match parse_author_origin_selector_list_from_str(&selectors) {
|
|
Err(()) => Err(Syntax),
|
|
Ok(ref selectors) => {
|
|
Ok(matches(selectors, &Root::from_ref(self), None))
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-element-closest
|
|
fn Closest(self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
|
|
match parse_author_origin_selector_list_from_str(&selectors) {
|
|
Err(()) => Err(Syntax),
|
|
Ok(ref selectors) => {
|
|
let root = NodeCast::from_ref(self);
|
|
for element in root.inclusive_ancestors() {
|
|
if let Some(element) = ElementCast::to_root(element) {
|
|
if matches(selectors, &element, None) {
|
|
return Ok(Some(element));
|
|
}
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VirtualMethods for Element {
|
|
fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
|
|
let node: &Node = NodeCast::from_ref(self);
|
|
Some(node as &VirtualMethods)
|
|
}
|
|
|
|
fn after_set_attr(&self, attr: &Attr) {
|
|
if let Some(ref s) = self.super_type() {
|
|
s.after_set_attr(attr);
|
|
}
|
|
|
|
let node = NodeCast::from_ref(self);
|
|
match attr.local_name() {
|
|
&atom!("style") => {
|
|
// Modifying the `style` attribute might change style.
|
|
let doc = document_from_node(self);
|
|
let base_url = doc.r().base_url();
|
|
let value = attr.value();
|
|
let style = Some(parse_style_attribute(&value, &base_url));
|
|
*self.style_attribute.borrow_mut() = style;
|
|
|
|
if node.is_in_doc() {
|
|
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
|
|
}
|
|
}
|
|
&atom!("class") => {
|
|
// Modifying a class can change style.
|
|
if node.is_in_doc() {
|
|
let document = document_from_node(self);
|
|
document.r().content_changed(node, NodeDamage::NodeStyleDamaged);
|
|
}
|
|
}
|
|
&atom!("id") => {
|
|
// Modifying an ID might change style.
|
|
let value = attr.value();
|
|
if node.is_in_doc() {
|
|
let doc = document_from_node(self);
|
|
if !value.is_empty() {
|
|
let value = value.atom().unwrap().clone();
|
|
doc.r().register_named_element(self, value);
|
|
}
|
|
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
|
|
}
|
|
}
|
|
_ => {
|
|
// Modifying any other attribute might change arbitrary things.
|
|
if node.is_in_doc() {
|
|
let document = document_from_node(self);
|
|
document.r().content_changed(node, NodeDamage::OtherNodeDamage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn before_remove_attr(&self, attr: &Attr) {
|
|
if let Some(ref s) = self.super_type() {
|
|
s.before_remove_attr(attr);
|
|
}
|
|
|
|
let node = NodeCast::from_ref(self);
|
|
match attr.local_name() {
|
|
&atom!("style") => {
|
|
// Modifying the `style` attribute might change style.
|
|
*self.style_attribute.borrow_mut() = None;
|
|
|
|
if node.is_in_doc() {
|
|
let doc = document_from_node(self);
|
|
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
|
|
}
|
|
}
|
|
&atom!("id") => {
|
|
// Modifying an ID can change style.
|
|
let value = attr.value();
|
|
if node.is_in_doc() {
|
|
let doc = document_from_node(self);
|
|
if !value.is_empty() {
|
|
let value = value.atom().unwrap().clone();
|
|
doc.r().unregister_named_element(self, value);
|
|
}
|
|
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
|
|
}
|
|
}
|
|
&atom!("class") => {
|
|
// Modifying a class can change style.
|
|
if node.is_in_doc() {
|
|
let document = document_from_node(self);
|
|
document.r().content_changed(node, NodeDamage::NodeStyleDamaged);
|
|
}
|
|
}
|
|
_ => {
|
|
// Modifying any other attribute might change arbitrary things.
|
|
if node.is_in_doc() {
|
|
let doc = document_from_node(self);
|
|
doc.r().content_changed(node, NodeDamage::OtherNodeDamage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
|
|
match name {
|
|
&atom!("id") => AttrValue::from_atomic(value),
|
|
&atom!("class") => AttrValue::from_serialized_tokenlist(value),
|
|
_ => self.super_type().unwrap().parse_plain_attribute(name, 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);
|
|
}
|
|
|
|
if !tree_in_doc { return; }
|
|
|
|
if let Some(ref attr) = self.get_attribute(&ns!(""), &atom!("id")) {
|
|
let doc = document_from_node(self);
|
|
let value = attr.r().Value();
|
|
if !value.is_empty() {
|
|
let value = Atom::from_slice(&value);
|
|
doc.r().register_named_element(self, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
|
if let Some(ref s) = self.super_type() {
|
|
s.unbind_from_tree(tree_in_doc);
|
|
}
|
|
|
|
if !tree_in_doc { return; }
|
|
|
|
if let Some(ref attr) = self.get_attribute(&ns!(""), &atom!("id")) {
|
|
let doc = document_from_node(self);
|
|
let value = attr.r().Value();
|
|
if !value.is_empty() {
|
|
let value = Atom::from_slice(&value);
|
|
doc.r().unregister_named_element(self, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> ::selectors::Element for Root<Element> {
|
|
fn parent_element(&self) -> Option<Root<Element>> {
|
|
NodeCast::from_ref(&**self).GetParentElement()
|
|
}
|
|
|
|
fn first_child_element(&self) -> Option<Root<Element>> {
|
|
self.node.child_elements().next()
|
|
}
|
|
|
|
fn last_child_element(&self) -> Option<Root<Element>> {
|
|
self.node.rev_children().filter_map(ElementCast::to_root).next()
|
|
}
|
|
|
|
fn prev_sibling_element(&self) -> Option<Root<Element>> {
|
|
self.node.preceding_siblings().filter_map(ElementCast::to_root).next()
|
|
}
|
|
|
|
fn next_sibling_element(&self) -> Option<Root<Element>> {
|
|
self.node.following_siblings().filter_map(ElementCast::to_root).next()
|
|
}
|
|
|
|
fn is_root(&self) -> bool {
|
|
match self.node.GetParentNode() {
|
|
None => false,
|
|
Some(node) => node.is_document(),
|
|
}
|
|
}
|
|
|
|
fn is_empty(&self) -> bool {
|
|
self.node.children().all(|node| !node.is_element() && match TextCast::to_ref(&*node) {
|
|
None => true,
|
|
Some(text) => CharacterDataCast::from_ref(text).data().is_empty()
|
|
})
|
|
}
|
|
|
|
fn is_link(&self) -> bool {
|
|
// FIXME: This is HTML only.
|
|
let node = NodeCast::from_ref(&**self);
|
|
match node.type_id() {
|
|
// https://html.spec.whatwg.org/multipage/#selector-link
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) |
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => {
|
|
self.has_attribute(&atom!("href"))
|
|
},
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn is_unvisited_link(&self) -> bool {
|
|
self.is_link()
|
|
}
|
|
|
|
#[inline]
|
|
fn is_visited_link(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn get_local_name<'b>(&'b self) -> &'b Atom {
|
|
self.local_name()
|
|
}
|
|
fn get_namespace<'b>(&'b self) -> &'b Namespace {
|
|
self.namespace()
|
|
}
|
|
fn get_hover_state(&self) -> bool {
|
|
let node = NodeCast::from_ref(&**self);
|
|
node.get_hover_state()
|
|
}
|
|
fn get_active_state(&self) -> bool {
|
|
let node = NodeCast::from_ref(&**self);
|
|
node.get_active_state()
|
|
}
|
|
fn get_focus_state(&self) -> bool {
|
|
// TODO: Also check whether the top-level browsing context has the system focus,
|
|
// and whether this element is a browsing context container.
|
|
// https://html.spec.whatwg.org/multipage/#selector-focus
|
|
let node = NodeCast::from_ref(&**self);
|
|
node.get_focus_state()
|
|
}
|
|
fn get_id(&self) -> Option<Atom> {
|
|
self.get_attribute(&ns!(""), &atom!("id")).map(|attr| {
|
|
match *attr.r().value() {
|
|
AttrValue::Atom(ref val) => val.clone(),
|
|
_ => panic!("`id` attribute should be AttrValue::Atom"),
|
|
}
|
|
})
|
|
}
|
|
fn get_disabled_state(&self) -> bool {
|
|
let node = NodeCast::from_ref(&**self);
|
|
node.get_disabled_state()
|
|
}
|
|
fn get_enabled_state(&self) -> bool {
|
|
let node = NodeCast::from_ref(&**self);
|
|
node.get_enabled_state()
|
|
}
|
|
fn get_checked_state(&self) -> bool {
|
|
let input_element: Option<&HTMLInputElement> = HTMLInputElementCast::to_ref(&**self);
|
|
match input_element {
|
|
Some(input) => input.Checked(),
|
|
None => false,
|
|
}
|
|
}
|
|
fn get_indeterminate_state(&self) -> bool {
|
|
let input_element: Option<&HTMLInputElement> = HTMLInputElementCast::to_ref(&**self);
|
|
match input_element {
|
|
Some(input) => input.get_indeterminate_state(),
|
|
None => false,
|
|
}
|
|
}
|
|
fn has_class(&self, name: &Atom) -> bool {
|
|
AttributeHandlers::has_class(&**self, name)
|
|
}
|
|
fn each_class<F>(&self, mut callback: F)
|
|
where F: FnMut(&Atom)
|
|
{
|
|
if let Some(ref attr) = self.get_attribute(&ns!(""), &atom!("class")) {
|
|
if let Some(tokens) = attr.r().value().tokens() {
|
|
for token in tokens {
|
|
callback(token)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fn has_servo_nonzero_border(&self) -> bool {
|
|
let table_element: Option<&HTMLTableElement> = HTMLTableElementCast::to_ref(&**self);
|
|
match table_element {
|
|
None => false,
|
|
Some(this) => {
|
|
match this.get_border() {
|
|
None | Some(0) => false,
|
|
Some(_) => true,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool
|
|
where F: Fn(&str) -> bool
|
|
{
|
|
let local_name = {
|
|
if self.is_html_element_in_html_document() {
|
|
&attr.lower_name
|
|
} else {
|
|
&attr.name
|
|
}
|
|
};
|
|
match attr.namespace {
|
|
NamespaceConstraint::Specific(ref ns) => {
|
|
self.get_attribute(ns, local_name)
|
|
.map_or(false, |attr| {
|
|
test(&attr.r().value())
|
|
})
|
|
},
|
|
NamespaceConstraint::Any => {
|
|
let mut attributes: RootedVec<JS<Attr>> = RootedVec::new();
|
|
self.get_attributes(local_name, &mut attributes);
|
|
attributes.iter().any(|attr| {
|
|
// FIXME(https://github.com/rust-lang/rust/issues/23338)
|
|
let attr = attr.root();
|
|
let value = attr.r().value();
|
|
test(&value)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_html_element_in_html_document(&self) -> bool {
|
|
self.html_element_in_html_document()
|
|
}
|
|
}
|
|
|
|
|
|
impl Element {
|
|
pub fn as_maybe_activatable<'a>(&'a self) -> Option<&'a (Activatable + 'a)> {
|
|
let node = NodeCast::from_ref(self);
|
|
let element = match node.type_id() {
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => {
|
|
let element = HTMLInputElementCast::to_ref(self).unwrap();
|
|
Some(element as &'a (Activatable + 'a))
|
|
},
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) => {
|
|
let element = HTMLAnchorElementCast::to_ref(self).unwrap();
|
|
Some(element as &'a (Activatable + 'a))
|
|
},
|
|
_ => {
|
|
None
|
|
}
|
|
};
|
|
element.and_then(|elem| {
|
|
if elem.is_instance_activatable() {
|
|
Some(elem)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn click_in_progress(&self) -> bool {
|
|
let node = NodeCast::from_ref(self);
|
|
node.get_flag(CLICK_IN_PROGRESS)
|
|
}
|
|
|
|
pub fn set_click_in_progress(&self, click: bool) {
|
|
let node = NodeCast::from_ref(self);
|
|
node.set_flag(CLICK_IN_PROGRESS, click)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#nearest-activatable-element
|
|
pub fn nearest_activable_element(&self) -> Option<Root<Element>> {
|
|
match self.as_maybe_activatable() {
|
|
Some(el) => Some(Root::from_ref(el.as_element())),
|
|
None => {
|
|
let node = NodeCast::from_ref(self);
|
|
for node in node.ancestors() {
|
|
if let Some(node) = ElementCast::to_ref(node.r()) {
|
|
if node.as_maybe_activatable().is_some() {
|
|
return Some(Root::from_ref(node))
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Please call this method *only* for real click events
|
|
///
|
|
/// https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps
|
|
///
|
|
/// Use an element's synthetic click activation (or handle_event) for any script-triggered clicks.
|
|
/// If the spec says otherwise, check with Manishearth first
|
|
pub fn authentic_click_activation<'b>(&self, event: &'b Event) {
|
|
// Not explicitly part of the spec, however this helps enforce the invariants
|
|
// required to save state between pre-activation and post-activation
|
|
// since we cannot nest authentic clicks (unlike synthetic click activation, where
|
|
// the script can generate more click events from the handler)
|
|
assert!(!self.click_in_progress());
|
|
|
|
let target = EventTargetCast::from_ref(self);
|
|
// Step 2 (requires canvas support)
|
|
// Step 3
|
|
self.set_click_in_progress(true);
|
|
// Step 4
|
|
let e = self.nearest_activable_element();
|
|
match e {
|
|
Some(ref el) => match el.r().as_maybe_activatable() {
|
|
Some(elem) => {
|
|
// Step 5-6
|
|
elem.pre_click_activation();
|
|
event.fire(target);
|
|
if !event.DefaultPrevented() {
|
|
// post click activation
|
|
elem.activation_behavior(event, target);
|
|
} else {
|
|
elem.canceled_activation();
|
|
}
|
|
}
|
|
// Step 6
|
|
None => {event.fire(target);}
|
|
},
|
|
// Step 6
|
|
None => {event.fire(target);}
|
|
}
|
|
// Step 7
|
|
self.set_click_in_progress(false);
|
|
}
|
|
}
|