1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::{self, CSSStyleDeclarationMethods}; 6 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; 7 use dom::bindings::error::{Error, ErrorResult, Fallible}; 8 use dom::bindings::inheritance::Castable; 9 use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; 10 use dom::bindings::root::{Dom, DomRoot}; 11 use dom::bindings::str::DOMString; 12 use dom::cssrule::CSSRule; 13 use dom::element::Element; 14 use dom::node::{Node, window_from_node, document_from_node}; 15 use dom::window::Window; 16 use dom_struct::dom_struct; 17 use servo_arc::Arc; 18 use servo_url::ServoUrl; 19 use style::attr::AttrValue; 20 use style::properties::{DeclarationSource, Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId}; 21 use style::properties::{parse_one_declaration_into, parse_style_attribute, SourcePropertyDeclaration}; 22 use style::selector_parser::PseudoElement; 23 use style::shared_lock::Locked; 24 use style_traits::ParsingMode; 25 26 // http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface 27 #[dom_struct] 28 pub struct CSSStyleDeclaration { 29 reflector_: Reflector, 30 owner: CSSStyleOwner, 31 readonly: bool, 32 pseudo: Option<PseudoElement>, 33 } 34 35 #[derive(JSTraceable, MallocSizeOf)] 36 #[must_root] 37 pub enum CSSStyleOwner { 38 Element(Dom<Element>), 39 CSSRule(Dom<CSSRule>, 40 #[ignore_malloc_size_of = "Arc"] 41 Arc<Locked<PropertyDeclarationBlock>>), 42 } 43 44 impl CSSStyleOwner { 45 // Mutate the declaration block associated to this style owner, and 46 // optionally indicate if it has changed (assumed to be true). mutate_associated_block<F, R>(&self, f: F) -> R where F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R,47 fn mutate_associated_block<F, R>(&self, f: F) -> R 48 where F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R, 49 { 50 // TODO(emilio): This has some duplication just to avoid dummy clones. 51 // 52 // This is somewhat complex but the complexity is encapsulated. 53 let mut changed = true; 54 match *self { 55 CSSStyleOwner::Element(ref el) => { 56 let document = document_from_node(&**el); 57 let shared_lock = document.style_shared_lock(); 58 let mut attr = el.style_attribute().borrow_mut().take(); 59 let result = if attr.is_some() { 60 let lock = attr.as_ref().unwrap(); 61 let mut guard = shared_lock.write(); 62 let mut pdb = lock.write_with(&mut guard); 63 let result = f(&mut pdb, &mut changed); 64 result 65 } else { 66 let mut pdb = PropertyDeclarationBlock::new(); 67 let result = f(&mut pdb, &mut changed); 68 69 // Here `changed` is somewhat silly, because we know the 70 // exact conditions under it changes. 71 changed = !pdb.declarations().is_empty(); 72 if changed { 73 attr = Some(Arc::new(shared_lock.wrap(pdb))); 74 } 75 76 result 77 }; 78 79 if changed { 80 // Note that there's no need to remove the attribute here if 81 // the declaration block is empty[1], and if `attr` is 82 // `None` it means that it necessarily didn't change, so no 83 // need to go through all the set_attribute machinery. 84 // 85 // [1]: https://github.com/whatwg/html/issues/2306 86 if let Some(pdb) = attr { 87 let guard = shared_lock.read(); 88 let mut serialization = String::new(); 89 pdb.read_with(&guard).to_css(&mut serialization).unwrap(); 90 el.set_attribute(&local_name!("style"), 91 AttrValue::Declaration(serialization, 92 pdb)); 93 } 94 } else { 95 // Remember to put it back. 96 *el.style_attribute().borrow_mut() = attr; 97 } 98 99 result 100 } 101 CSSStyleOwner::CSSRule(ref rule, ref pdb) => { 102 let result = { 103 let mut guard = rule.shared_lock().write(); 104 f(&mut *pdb.write_with(&mut guard), &mut changed) 105 }; 106 if changed { 107 // If this is changed, see also 108 // CSSStyleRule::SetSelectorText, which does the same thing. 109 rule.global().as_window().Document().invalidate_stylesheets(); 110 } 111 result 112 } 113 } 114 } 115 with_block<F, R>(&self, f: F) -> R where F: FnOnce(&PropertyDeclarationBlock) -> R,116 fn with_block<F, R>(&self, f: F) -> R 117 where F: FnOnce(&PropertyDeclarationBlock) -> R, 118 { 119 match *self { 120 CSSStyleOwner::Element(ref el) => { 121 match *el.style_attribute().borrow() { 122 Some(ref pdb) => { 123 let document = document_from_node(&**el); 124 let guard = document.style_shared_lock().read(); 125 f(pdb.read_with(&guard)) 126 } 127 None => { 128 let pdb = PropertyDeclarationBlock::new(); 129 f(&pdb) 130 } 131 } 132 } 133 CSSStyleOwner::CSSRule(ref rule, ref pdb) => { 134 let guard = rule.shared_lock().read(); 135 f(pdb.read_with(&guard)) 136 } 137 } 138 } 139 window(&self) -> DomRoot<Window>140 fn window(&self) -> DomRoot<Window> { 141 match *self { 142 CSSStyleOwner::Element(ref el) => window_from_node(&**el), 143 CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()), 144 } 145 } 146 base_url(&self) -> ServoUrl147 fn base_url(&self) -> ServoUrl { 148 match *self { 149 CSSStyleOwner::Element(ref el) => window_from_node(&**el).Document().base_url(), 150 CSSStyleOwner::CSSRule(ref rule, _) => { 151 (*rule.parent_stylesheet().style_stylesheet().contents.url_data.read()).clone() 152 } 153 } 154 } 155 } 156 157 #[derive(MallocSizeOf, PartialEq)] 158 pub enum CSSModificationAccess { 159 ReadWrite, 160 Readonly, 161 } 162 163 macro_rules! css_properties( 164 ( $([$getter:ident, $setter:ident, $id:expr],)* ) => ( 165 $( 166 fn $getter(&self) -> DOMString { 167 debug_assert!( 168 $id.enabled_for_all_content(), 169 "Someone forgot a #[Pref] annotation" 170 ); 171 self.get_property_value($id) 172 } 173 fn $setter(&self, value: DOMString) -> ErrorResult { 174 debug_assert!( 175 $id.enabled_for_all_content(), 176 "Someone forgot a #[Pref] annotation" 177 ); 178 self.set_property($id, value, DOMString::new()) 179 } 180 )* 181 ); 182 ); 183 184 impl CSSStyleDeclaration { 185 #[allow(unrooted_must_root)] new_inherited(owner: CSSStyleOwner, pseudo: Option<PseudoElement>, modification_access: CSSModificationAccess) -> CSSStyleDeclaration186 pub fn new_inherited(owner: CSSStyleOwner, 187 pseudo: Option<PseudoElement>, 188 modification_access: CSSModificationAccess) 189 -> CSSStyleDeclaration { 190 CSSStyleDeclaration { 191 reflector_: Reflector::new(), 192 owner: owner, 193 readonly: modification_access == CSSModificationAccess::Readonly, 194 pseudo: pseudo, 195 } 196 } 197 198 #[allow(unrooted_must_root)] new(global: &Window, owner: CSSStyleOwner, pseudo: Option<PseudoElement>, modification_access: CSSModificationAccess) -> DomRoot<CSSStyleDeclaration>199 pub fn new(global: &Window, 200 owner: CSSStyleOwner, 201 pseudo: Option<PseudoElement>, 202 modification_access: CSSModificationAccess) 203 -> DomRoot<CSSStyleDeclaration> { 204 reflect_dom_object( 205 Box::new(CSSStyleDeclaration::new_inherited(owner, pseudo, modification_access)), 206 global, 207 CSSStyleDeclarationBinding::Wrap 208 ) 209 } 210 get_computed_style(&self, property: PropertyId) -> DOMString211 fn get_computed_style(&self, property: PropertyId) -> DOMString { 212 match self.owner { 213 CSSStyleOwner::CSSRule(..) => 214 panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner"), 215 CSSStyleOwner::Element(ref el) => { 216 let node = el.upcast::<Node>(); 217 if !node.is_in_doc() { 218 // TODO: Node should be matched against the style rules of this window. 219 // Firefox is currently the only browser to implement this. 220 return DOMString::new(); 221 } 222 let addr = node.to_trusted_node_address(); 223 window_from_node(node).resolved_style_query(addr, self.pseudo.clone(), property) 224 } 225 } 226 } 227 get_property_value(&self, id: PropertyId) -> DOMString228 fn get_property_value(&self, id: PropertyId) -> DOMString { 229 if self.readonly { 230 // Readonly style declarations are used for getComputedStyle. 231 return self.get_computed_style(id); 232 } 233 234 let mut string = String::new(); 235 236 self.owner.with_block(|pdb| { 237 pdb.property_value_to_css(&id, &mut string).unwrap(); 238 }); 239 240 DOMString::from(string) 241 } 242 set_property(&self, id: PropertyId, value: DOMString, priority: DOMString) -> ErrorResult243 fn set_property(&self, id: PropertyId, value: DOMString, priority: DOMString) -> ErrorResult { 244 // Step 1 245 if self.readonly { 246 return Err(Error::NoModificationAllowed); 247 } 248 249 if !id.enabled_for_all_content() { 250 return Ok(()); 251 } 252 253 self.owner.mutate_associated_block(|pdb, changed| { 254 if value.is_empty() { 255 // Step 3 256 *changed = pdb.remove_property(&id); 257 return Ok(()); 258 } 259 260 // Step 4 261 let importance = match &*priority { 262 "" => Importance::Normal, 263 p if p.eq_ignore_ascii_case("important") => Importance::Important, 264 _ => { 265 *changed = false; 266 return Ok(()); 267 } 268 }; 269 270 // Step 5 271 let window = self.owner.window(); 272 let quirks_mode = window.Document().quirks_mode(); 273 let mut declarations = SourcePropertyDeclaration::new(); 274 let result = parse_one_declaration_into( 275 &mut declarations, id, &value, &self.owner.base_url(), 276 window.css_error_reporter(), ParsingMode::DEFAULT, quirks_mode); 277 278 // Step 6 279 match result { 280 Ok(()) => {}, 281 Err(_) => { 282 *changed = false; 283 return Ok(()); 284 } 285 } 286 287 // Step 7 288 // Step 8 289 *changed = pdb.extend( 290 declarations.drain(), 291 importance, 292 DeclarationSource::CssOm, 293 ); 294 295 Ok(()) 296 }) 297 } 298 } 299 300 impl CSSStyleDeclarationMethods for CSSStyleDeclaration { 301 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length Length(&self) -> u32302 fn Length(&self) -> u32 { 303 self.owner.with_block(|pdb| { 304 pdb.declarations().len() as u32 305 }) 306 } 307 308 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item Item(&self, index: u32) -> DOMString309 fn Item(&self, index: u32) -> DOMString { 310 self.IndexedGetter(index).unwrap_or_default() 311 } 312 313 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue GetPropertyValue(&self, property: DOMString) -> DOMString314 fn GetPropertyValue(&self, property: DOMString) -> DOMString { 315 let id = if let Ok(id) = PropertyId::parse(&property) { 316 id 317 } else { 318 // Unkwown property 319 return DOMString::new() 320 }; 321 self.get_property_value(id) 322 } 323 324 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority GetPropertyPriority(&self, property: DOMString) -> DOMString325 fn GetPropertyPriority(&self, property: DOMString) -> DOMString { 326 let id = if let Ok(id) = PropertyId::parse(&property) { 327 id 328 } else { 329 // Unkwown property 330 return DOMString::new() 331 }; 332 333 self.owner.with_block(|pdb| { 334 if pdb.property_priority(&id).important() { 335 DOMString::from("important") 336 } else { 337 // Step 4 338 DOMString::new() 339 } 340 }) 341 } 342 343 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty SetProperty(&self, property: DOMString, value: DOMString, priority: DOMString) -> ErrorResult344 fn SetProperty(&self, 345 property: DOMString, 346 value: DOMString, 347 priority: DOMString) 348 -> ErrorResult { 349 // Step 3 350 let id = if let Ok(id) = PropertyId::parse(&property) { 351 id 352 } else { 353 // Unknown property 354 return Ok(()) 355 }; 356 self.set_property(id, value, priority) 357 } 358 359 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult360 fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult { 361 // Step 1 362 if self.readonly { 363 return Err(Error::NoModificationAllowed); 364 } 365 366 // Step 2 & 3 367 let id = match PropertyId::parse(&property) { 368 Ok(id) => id, 369 Err(..) => return Ok(()), // Unkwown property 370 }; 371 372 // Step 4 373 let importance = match &*priority { 374 "" => Importance::Normal, 375 p if p.eq_ignore_ascii_case("important") => Importance::Important, 376 _ => return Ok(()), 377 }; 378 379 self.owner.mutate_associated_block(|pdb, changed| { 380 // Step 5 & 6 381 *changed = pdb.set_importance(&id, importance); 382 }); 383 384 Ok(()) 385 } 386 387 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertyvalue SetPropertyValue(&self, property: DOMString, value: DOMString) -> ErrorResult388 fn SetPropertyValue(&self, property: DOMString, value: DOMString) -> ErrorResult { 389 self.SetProperty(property, value, DOMString::new()) 390 } 391 392 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty RemoveProperty(&self, property: DOMString) -> Fallible<DOMString>393 fn RemoveProperty(&self, property: DOMString) -> Fallible<DOMString> { 394 // Step 1 395 if self.readonly { 396 return Err(Error::NoModificationAllowed); 397 } 398 399 let id = if let Ok(id) = PropertyId::parse(&property) { 400 id 401 } else { 402 // Unkwown property, cannot be there to remove. 403 return Ok(DOMString::new()) 404 }; 405 406 let mut string = String::new(); 407 self.owner.mutate_associated_block(|pdb, changed| { 408 pdb.property_value_to_css(&id, &mut string).unwrap(); 409 *changed = pdb.remove_property(&id); 410 }); 411 412 // Step 6 413 Ok(DOMString::from(string)) 414 } 415 416 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat CssFloat(&self) -> DOMString417 fn CssFloat(&self) -> DOMString { 418 self.GetPropertyValue(DOMString::from("float")) 419 } 420 421 // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat SetCssFloat(&self, value: DOMString) -> ErrorResult422 fn SetCssFloat(&self, value: DOMString) -> ErrorResult { 423 self.SetPropertyValue(DOMString::from("float"), value) 424 } 425 426 // https://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface IndexedGetter(&self, index: u32) -> Option<DOMString>427 fn IndexedGetter(&self, index: u32) -> Option<DOMString> { 428 self.owner.with_block(|pdb| { 429 pdb.declarations().get(index as usize).map(|declaration| { 430 let important = pdb.declarations_importance().get(index); 431 let mut css = String::new(); 432 declaration.to_css(&mut css).unwrap(); 433 if important { 434 css += " !important"; 435 } 436 DOMString::from(css) 437 }) 438 }) 439 } 440 441 // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext CssText(&self) -> DOMString442 fn CssText(&self) -> DOMString { 443 self.owner.with_block(|pdb| { 444 let mut serialization = String::new(); 445 pdb.to_css(&mut serialization).unwrap(); 446 DOMString::from(serialization) 447 }) 448 } 449 450 // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext SetCssText(&self, value: DOMString) -> ErrorResult451 fn SetCssText(&self, value: DOMString) -> ErrorResult { 452 let window = self.owner.window(); 453 454 // Step 1 455 if self.readonly { 456 return Err(Error::NoModificationAllowed); 457 } 458 459 let quirks_mode = window.Document().quirks_mode(); 460 self.owner.mutate_associated_block(|pdb, _changed| { 461 // Step 3 462 *pdb = parse_style_attribute(&value, 463 &self.owner.base_url(), 464 window.css_error_reporter(), 465 quirks_mode); 466 }); 467 468 Ok(()) 469 } 470 471 // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-_camel_cased_attribute 472 css_properties_accessors!(css_properties); 473 } 474