/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::{self, CSSStyleDeclarationMethods}; use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, Root}; use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::str::DOMString; use dom::element::Element; use dom::node::{Node, NodeDamage, window_from_node}; use dom::window::Window; use parking_lot::RwLock; use servo_atoms::Atom; use std::ascii::AsciiExt; use std::sync::Arc; use style::parser::ParserContextExtraData; use style::properties::{Shorthand, Importance, PropertyDeclarationBlock}; use style::properties::{is_supported_property, parse_one_declaration, parse_style_attribute}; use style::selector_impl::PseudoElement; use style_traits::ToCss; // http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface #[dom_struct] pub struct CSSStyleDeclaration { reflector_: Reflector, owner: JS, readonly: bool, pseudo: Option, } #[derive(PartialEq, HeapSizeOf)] pub enum CSSModificationAccess { ReadWrite, Readonly, } macro_rules! css_properties( ( $([$getter:ident, $setter:ident, $cssprop:expr]),* ) => ( $( fn $getter(&self) -> DOMString { self.GetPropertyValue(DOMString::from($cssprop)) } fn $setter(&self, value: DOMString) -> ErrorResult { self.SetPropertyValue(DOMString::from($cssprop), value) } )* ); ); impl CSSStyleDeclaration { pub fn new_inherited(owner: &Element, pseudo: Option, modification_access: CSSModificationAccess) -> CSSStyleDeclaration { CSSStyleDeclaration { reflector_: Reflector::new(), owner: JS::from_ref(owner), readonly: modification_access == CSSModificationAccess::Readonly, pseudo: pseudo, } } pub fn new(global: &Window, owner: &Element, pseudo: Option, modification_access: CSSModificationAccess) -> Root { reflect_dom_object(box CSSStyleDeclaration::new_inherited(owner, pseudo, modification_access), global, CSSStyleDeclarationBinding::Wrap) } fn get_computed_style(&self, property: &Atom) -> Option { let node = self.owner.upcast::(); if !node.is_in_doc() { // TODO: Node should be matched against the style rules of this window. // Firefox is currently the only browser to implement this. return None; } let addr = node.to_trusted_node_address(); window_from_node(&*self.owner).resolved_style_query(addr, self.pseudo.clone(), property) } } impl CSSStyleDeclarationMethods for CSSStyleDeclaration { // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length fn Length(&self) -> u32 { let elem = self.owner.upcast::(); let len = match *elem.style_attribute().borrow() { Some(ref lock) => lock.read().declarations.len(), None => 0, }; len as u32 } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item fn Item(&self, index: u32) -> DOMString { self.IndexedGetter(index).unwrap_or_default() } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue fn GetPropertyValue(&self, mut property: DOMString) -> DOMString { if self.readonly { // Readonly style declarations are used for getComputedStyle. property.make_ascii_lowercase(); let property = Atom::from(property); return self.get_computed_style(&property).unwrap_or(DOMString::new()); } let style_attribute = self.owner.style_attribute().borrow(); let style_attribute = if let Some(ref lock) = *style_attribute { lock.read() } else { // No style attribute is like an empty style attribute: no matching declaration. return DOMString::new() }; let mut string = String::new(); style_attribute.property_value_to_css(&property, &mut string).unwrap(); DOMString::from(string) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority fn GetPropertyPriority(&self, property: DOMString) -> DOMString { let style_attribute = self.owner.style_attribute().borrow(); let style_attribute = if let Some(ref lock) = *style_attribute { lock.read() } else { // No style attribute is like an empty style attribute: no matching declaration. return DOMString::new() }; if style_attribute.property_priority(&property).important() { DOMString::from("important") } else { // Step 4 DOMString::new() } } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty fn SetProperty(&self, property: DOMString, value: DOMString, priority: DOMString) -> ErrorResult { // Step 1 if self.readonly { return Err(Error::NoModificationAllowed); } // Step 3 if !is_supported_property(&property) { return Ok(()); } let mut style_attribute = self.owner.style_attribute().borrow_mut(); if value.is_empty() { // Step 4 let empty; { let mut style_attribute = if let Some(ref lock) = *style_attribute { lock.write() } else { // No style attribute is like an empty style attribute: nothing to remove. return Ok(()) }; style_attribute.remove_property(&property); empty = style_attribute.declarations.is_empty() } if empty { *style_attribute = None; } } else { // Step 5 let importance = match &*priority { "" => Importance::Normal, p if p.eq_ignore_ascii_case("important") => Importance::Important, _ => return Ok(()), }; // Step 6 let window = window_from_node(&*self.owner); let declarations = parse_one_declaration(&property, &value, &window.get_url(), window.css_error_reporter(), ParserContextExtraData::default()); // Step 7 let declarations = if let Ok(declarations) = declarations { declarations } else { return Ok(()); }; // Step 8 // Step 9 match *style_attribute { Some(ref lock) => { let mut style_attribute = lock.write(); for declaration in declarations { style_attribute.set_parsed_declaration(declaration, importance); } self.owner.set_style_attr(style_attribute.to_css_string()); } ref mut option @ None => { let important_count = if importance.important() { declarations.len() as u32 } else { 0 }; let block = PropertyDeclarationBlock { declarations: declarations.into_iter().map(|d| (d, importance)).collect(), important_count: important_count, }; self.owner.set_style_attr(block.to_css_string()); *option = Some(Arc::new(RwLock::new(block))); } } } let node = self.owner.upcast::(); node.dirty(NodeDamage::NodeStyleDamaged); Ok(()) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult { // Step 1 if self.readonly { return Err(Error::NoModificationAllowed); } // Step 2 & 3 if !is_supported_property(&property) { return Ok(()); } // Step 4 let importance = match &*priority { "" => Importance::Normal, p if p.eq_ignore_ascii_case("important") => Importance::Important, _ => return Ok(()), }; let style_attribute = self.owner.style_attribute().borrow(); if let Some(ref lock) = *style_attribute { let mut style_attribute = lock.write(); // Step 5 & 6 match Shorthand::from_name(&property) { Some(shorthand) => style_attribute.set_importance(shorthand.longhands(), importance), None => style_attribute.set_importance(&[&*property], importance), } self.owner.set_style_attr(style_attribute.to_css_string()); let node = self.owner.upcast::(); node.dirty(NodeDamage::NodeStyleDamaged); } Ok(()) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertyvalue fn SetPropertyValue(&self, property: DOMString, value: DOMString) -> ErrorResult { self.SetProperty(property, value, DOMString::new()) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty fn RemoveProperty(&self, property: DOMString) -> Fallible { // Step 1 if self.readonly { return Err(Error::NoModificationAllowed); } let mut style_attribute = self.owner.style_attribute().borrow_mut(); let mut string = String::new(); let empty; { let mut style_attribute = if let Some(ref lock) = *style_attribute { lock.write() } else { // No style attribute is like an empty style attribute: nothing to remove. return Ok(DOMString::new()) }; // Step 3 style_attribute.property_value_to_css(&property, &mut string).unwrap(); // Step 4 & 5 style_attribute.remove_property(&property); self.owner.set_style_attr(style_attribute.to_css_string()); empty = style_attribute.declarations.is_empty() } if empty { *style_attribute = None; } let node = self.owner.upcast::(); node.dirty(NodeDamage::NodeStyleDamaged); // Step 6 Ok(DOMString::from(string)) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat fn CssFloat(&self) -> DOMString { self.GetPropertyValue(DOMString::from("float")) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat fn SetCssFloat(&self, value: DOMString) -> ErrorResult { self.SetPropertyValue(DOMString::from("float"), value) } // https://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface fn IndexedGetter(&self, index: u32) -> Option { let index = index as usize; let elem = self.owner.upcast::(); let style_attribute = elem.style_attribute().borrow(); style_attribute.as_ref().and_then(|lock| { lock.read().declarations.get(index).map(|entry| { let (ref declaration, importance) = *entry; let mut css = declaration.to_css_string(); if importance.important() { css += " !important"; } DOMString::from(css) }) }) } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext fn CssText(&self) -> DOMString { let elem = self.owner.upcast::(); let style_attribute = elem.style_attribute().borrow(); if let Some(lock) = style_attribute.as_ref() { DOMString::from(lock.read().to_css_string()) } else { DOMString::new() } } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext fn SetCssText(&self, value: DOMString) -> ErrorResult { let window = window_from_node(self.owner.upcast::()); // Step 1 if self.readonly { return Err(Error::NoModificationAllowed); } // Step 3 let decl_block = parse_style_attribute(&value, &window.get_url(), window.css_error_reporter(), ParserContextExtraData::default()); *self.owner.style_attribute().borrow_mut() = if decl_block.declarations.is_empty() { self.owner.set_style_attr(String::new()); None // Step 2 } else { self.owner.set_style_attr(decl_block.to_css_string()); Some(Arc::new(RwLock::new(decl_block))) }; let node = self.owner.upcast::(); node.dirty(NodeDamage::NodeStyleDamaged); Ok(()) } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-_camel_cased_attribute css_properties_accessors!(css_properties); }