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