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 https://mozilla.org/MPL/2.0/. */
4 
5 use cssparser::{self, SourceLocation};
6 use html5ever::{Namespace as NsAtom};
7 use parking_lot::RwLock;
8 use selectors::attr::*;
9 use selectors::parser::*;
10 use servo_arc::Arc;
11 use servo_atoms::Atom;
12 use servo_config::prefs::{PREFS, PrefValue};
13 use servo_url::ServoUrl;
14 use std::borrow::ToOwned;
15 use std::cell::RefCell;
16 use std::sync::atomic::AtomicBool;
17 use style::context::QuirksMode;
18 use style::error_reporting::{ParseErrorReporter, ContextualParseError};
19 use style::media_queries::MediaList;
20 use style::properties::{CSSWideKeyword, CustomDeclaration};
21 use style::properties::{CustomDeclarationValue, Importance};
22 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
23 use style::properties::longhands::{self, animation_timing_function};
24 use style::shared_lock::SharedRwLock;
25 use style::stylesheets::{Origin, Namespaces};
26 use style::stylesheets::{Stylesheet, StylesheetContents, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
27 use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframePercentage};
28 use style::values::{KeyframesName, CustomIdent};
29 use style::values::computed::Percentage;
30 use style::values::specified::{LengthPercentageOrAuto, PositionComponent};
31 use style::values::specified::TimingFunction;
32 
block_from<I>(iterable: I) -> PropertyDeclarationBlock where I: IntoIterator<Item=(PropertyDeclaration, Importance)>33 pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
34 where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
35     let mut block = PropertyDeclarationBlock::new();
36     for (d, i) in iterable {
37         block.push(d, i);
38     }
39     block
40 }
41 
42 #[test]
test_parse_stylesheet()43 fn test_parse_stylesheet() {
44     let css = r"
45         @namespace url(http://www.w3.org/1999/xhtml);
46         /* FIXME: only if scripting is enabled */
47         input[type=hidden i] {
48             display: block !important;
49             display: none !important;
50             display: inline;
51             --a: b !important;
52             --a: inherit !important;
53             --a: c;
54         }
55         html , body /**/ {
56             display: none;
57             display: block;
58         }
59         #d1 > .ok { background: blue; }
60         @keyframes foo {
61             from { width: 0% }
62             to {
63                 width: 100%;
64                 width: 50% !important; /* !important not allowed here */
65                 animation-name: 'foo'; /* animation properties not allowed here */
66                 animation-timing-function: ease; /* … except animation-timing-function */
67             }
68         }";
69     let url = ServoUrl::parse("about::test").unwrap();
70     let lock = SharedRwLock::new();
71     let media = Arc::new(lock.wrap(MediaList::empty()));
72     let stylesheet = Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock,
73                                           None, None, QuirksMode::NoQuirks, 0);
74     let mut namespaces = Namespaces::default();
75     namespaces.default = Some(ns!(html));
76     let expected = Stylesheet {
77         contents: StylesheetContents {
78             origin: Origin::UserAgent,
79             namespaces: RwLock::new(namespaces),
80             url_data: RwLock::new(url),
81             quirks_mode: QuirksMode::NoQuirks,
82             rules: CssRules::new(vec![
83                 CssRule::Namespace(Arc::new(stylesheet.shared_lock.wrap(NamespaceRule {
84                     prefix: None,
85                     url: NsAtom::from("http://www.w3.org/1999/xhtml"),
86                     source_location: SourceLocation {
87                         line: 1,
88                         column: 19,
89                     },
90                 }))),
91                 CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
92                     selectors: SelectorList::from_vec(vec!(
93                         Selector::from_vec(vec!(
94                             Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
95                             Component::LocalName(LocalName {
96                                 name: local_name!("input"),
97                                 lower_name: local_name!("input"),
98                             }),
99                             Component::AttributeInNoNamespace {
100                                 local_name: local_name!("type"),
101                                 operator: AttrSelectorOperator::Equal,
102                                 value: "hidden".to_owned(),
103                                 case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive,
104                                 never_matches: false,
105                             }
106                         ), (0 << 20) + (1 << 10) + (1 << 0))
107                     )),
108                     block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
109                         (
110                             PropertyDeclaration::Display(longhands::display::SpecifiedValue::None),
111                             Importance::Important,
112                         ),
113                         (
114                             PropertyDeclaration::Custom(CustomDeclaration {
115                                 name: Atom::from("a"),
116                                 value: CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit),
117                             }),
118                             Importance::Important,
119                         ),
120                     ]))),
121                     source_location: SourceLocation {
122                         line: 3,
123                         column: 9,
124                     },
125                 }))),
126                 CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
127                     selectors: SelectorList::from_vec(vec!(
128                         Selector::from_vec(vec!(
129                                 Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
130                                 Component::LocalName(LocalName {
131                                     name: local_name!("html"),
132                                     lower_name: local_name!("html"),
133                                 }),
134                             ), (0 << 20) + (0 << 10) + (1 << 0)),
135                         Selector::from_vec(vec!(
136                             Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
137                             Component::LocalName(LocalName {
138                                 name: local_name!("body"),
139                                 lower_name: local_name!("body"),
140                             })
141                             ), (0 << 20) + (0 << 10) + (1 << 0)
142                         ),
143                     )),
144                     block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
145                         (PropertyDeclaration::Display(longhands::display::SpecifiedValue::Block),
146                          Importance::Normal),
147                     ]))),
148                     source_location: SourceLocation {
149                         line: 11,
150                         column: 9,
151                     },
152                 }))),
153                 CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
154                     selectors: SelectorList::from_vec(vec!(
155                         Selector::from_vec(vec!(
156                             Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
157                             Component::ID(Atom::from("d1")),
158                             Component::Combinator(Combinator::Child),
159                             Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
160                             Component::Class(Atom::from("ok"))
161                         ), (1 << 20) + (1 << 10) + (0 << 0))
162                     )),
163                     block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
164                         (PropertyDeclaration::BackgroundColor(
165                             longhands::background_color::SpecifiedValue::Numeric {
166                                 authored: Some("blue".to_owned().into_boxed_str()),
167                                 parsed: cssparser::RGBA::new(0, 0, 255, 255),
168                             }
169                          ),
170                          Importance::Normal),
171                         (PropertyDeclaration::BackgroundPositionX(
172                             longhands::background_position_x::SpecifiedValue(
173                             vec![PositionComponent::zero()])),
174                          Importance::Normal),
175                         (PropertyDeclaration::BackgroundPositionY(
176                             longhands::background_position_y::SpecifiedValue(
177                             vec![PositionComponent::zero()])),
178                          Importance::Normal),
179                         (PropertyDeclaration::BackgroundRepeat(
180                             longhands::background_repeat::SpecifiedValue(
181                             vec![longhands::background_repeat::single_value
182                                                        ::get_initial_specified_value()])),
183                          Importance::Normal),
184                         (PropertyDeclaration::BackgroundAttachment(
185                             longhands::background_attachment::SpecifiedValue(
186                             vec![longhands::background_attachment::single_value
187                                                        ::get_initial_specified_value()])),
188                          Importance::Normal),
189                         (PropertyDeclaration::BackgroundImage(
190                             longhands::background_image::SpecifiedValue(
191                             vec![longhands::background_image::single_value
192                                                        ::get_initial_specified_value()])),
193                          Importance::Normal),
194                         (PropertyDeclaration::BackgroundSize(
195                             longhands::background_size::SpecifiedValue(
196                             vec![longhands::background_size::single_value
197                                                        ::get_initial_specified_value()])),
198                          Importance::Normal),
199                         (PropertyDeclaration::BackgroundOrigin(
200                             longhands::background_origin::SpecifiedValue(
201                             vec![longhands::background_origin::single_value
202                                                        ::get_initial_specified_value()])),
203                          Importance::Normal),
204                         (PropertyDeclaration::BackgroundClip(
205                             longhands::background_clip::SpecifiedValue(
206                             vec![longhands::background_clip::single_value
207                                                        ::get_initial_specified_value()])),
208                          Importance::Normal),
209                     ]))),
210                     source_location: SourceLocation {
211                         line: 15,
212                         column: 9,
213                     },
214                 }))),
215                 CssRule::Keyframes(Arc::new(stylesheet.shared_lock.wrap(KeyframesRule {
216                     name: KeyframesName::Ident(CustomIdent("foo".into())),
217                     keyframes: vec![
218                         Arc::new(stylesheet.shared_lock.wrap(Keyframe {
219                             selector: KeyframeSelector::new_for_unit_testing(
220                                           vec![KeyframePercentage::new(0.)]),
221                             block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
222                                 (PropertyDeclaration::Width(
223                                     LengthPercentageOrAuto::Percentage(Percentage(0.))),
224                                  Importance::Normal),
225                             ]))),
226                             source_location: SourceLocation {
227                                 line: 17,
228                                 column: 13,
229                             },
230                         })),
231                         Arc::new(stylesheet.shared_lock.wrap(Keyframe {
232                             selector: KeyframeSelector::new_for_unit_testing(
233                                           vec![KeyframePercentage::new(1.)]),
234                             block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
235                                 (PropertyDeclaration::Width(
236                                     LengthPercentageOrAuto::Percentage(Percentage(1.))),
237                                  Importance::Normal),
238                                 (PropertyDeclaration::AnimationTimingFunction(
239                                     animation_timing_function::SpecifiedValue(
240                                         vec![TimingFunction::ease()])),
241                                  Importance::Normal),
242                             ]))),
243                             source_location: SourceLocation {
244                                 line: 18,
245                                 column: 13,
246                             },
247                         })),
248                     ],
249                     vendor_prefix: None,
250                     source_location: SourceLocation {
251                         line: 16,
252                         column: 19,
253                     },
254                 })))
255             ], &stylesheet.shared_lock),
256             source_map_url: RwLock::new(None),
257             source_url: RwLock::new(None),
258         },
259         media: Arc::new(stylesheet.shared_lock.wrap(MediaList::empty())),
260         shared_lock: stylesheet.shared_lock.clone(),
261         disabled: AtomicBool::new(false),
262     };
263 
264     assert_eq!(format!("{:#?}", stylesheet), format!("{:#?}", expected));
265 }
266 
267 #[derive(Debug)]
268 struct CSSError {
269     pub url : ServoUrl,
270     pub line: u32,
271     pub column: u32,
272     pub message: String
273 }
274 
275 struct TestingErrorReporter {
276     errors: RefCell<Vec<CSSError>>,
277 }
278 
279 impl TestingErrorReporter {
new() -> Self280     pub fn new() -> Self {
281         TestingErrorReporter {
282             errors: RefCell::new(Vec::new()),
283         }
284     }
285 
assert_messages_contain(&self, expected_errors: &[(u32, u32, &str)])286     fn assert_messages_contain(&self, expected_errors: &[(u32, u32, &str)]) {
287         let errors = self.errors.borrow();
288         for (i, (error, &(line, column, message))) in errors.iter().zip(expected_errors).enumerate() {
289             assert_eq!((error.line, error.column), (line, column),
290                        "line/column numbers of the {}th error: {:?}", i + 1, error.message);
291             assert!(error.message.contains(message),
292                     "{:?} does not contain {:?}", error.message, message);
293         }
294         if errors.len() < expected_errors.len() {
295             panic!("Missing errors: {:#?}", &expected_errors[errors.len()..]);
296         }
297         if errors.len() > expected_errors.len() {
298             panic!("Extra errors: {:#?}", &errors[expected_errors.len()..]);
299         }
300     }
301 }
302 
303 impl ParseErrorReporter for TestingErrorReporter {
report_error(&self, url: &ServoUrl, location: SourceLocation, error: ContextualParseError)304     fn report_error(&self,
305                     url: &ServoUrl,
306                     location: SourceLocation,
307                     error: ContextualParseError) {
308         self.errors.borrow_mut().push(
309             CSSError{
310                 url: url.clone(),
311                 line: location.line,
312                 column: location.column,
313                 message: error.to_string(),
314             }
315         )
316     }
317 }
318 
319 
320 #[test]
test_report_error_stylesheet()321 fn test_report_error_stylesheet() {
322     PREFS.set("layout.viewport.enabled", PrefValue::Boolean(true));
323     let css = r"
324     div {
325         background-color: red;
326         display: invalid;
327         background-image: linear-gradient(0deg, black, invalid, transparent);
328         invalid: true;
329     }
330     @media (min-width: 10px invalid 1000px) {}
331     @font-face { src: url(), invalid, url(); }
332     @counter-style foo { symbols: a 0invalid b }
333     @font-feature-values Sans Sans { @foo {} @swash { foo: 1 invalid 2 } }
334     @invalid;
335     @media screen { @invalid; }
336     @supports (color: green) and invalid and (margin: 0) {}
337     @keyframes foo { from invalid {} to { margin: 0 invalid 0; } }
338     @viewport { width: 320px invalid auto; }
339     ";
340     let url = ServoUrl::parse("about::test").unwrap();
341     let error_reporter = TestingErrorReporter::new();
342 
343     let lock = SharedRwLock::new();
344     let media = Arc::new(lock.wrap(MediaList::empty()));
345     Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock,
346                          None, Some(&error_reporter), QuirksMode::NoQuirks, 5);
347 
348     error_reporter.assert_messages_contain(&[
349         (8, 18, "Unsupported property declaration: 'display: invalid;'"),
350         (9, 27, "Unsupported property declaration: 'background-image:"),  // FIXME: column should be around 56
351         (10, 17, "Unsupported property declaration: 'invalid: true;'"),
352         (12, 28, "Invalid media rule"),
353         (13, 30, "Unsupported @font-face descriptor declaration"),
354 
355         // When @counter-style is supported, this should be replaced with two errors
356         (14, 19, "Invalid rule: '@counter-style "),
357 
358         // When @font-feature-values is supported, this should be replaced with two errors
359         (15, 25, "Invalid rule: '@font-feature-values "),
360 
361         (16, 13, "Invalid rule: '@invalid'"),
362         (17, 29, "Invalid rule: '@invalid'"),
363 
364         (18, 34, "Invalid rule: '@supports "),
365         (19, 26, "Invalid keyframe rule: 'from invalid '"),
366         (19, 52, "Unsupported keyframe property declaration: 'margin: 0 invalid 0;'"),
367         (20, 29, "Unsupported @viewport descriptor declaration: 'width: 320px invalid auto;'"),
368     ]);
369 
370     assert_eq!(error_reporter.errors.borrow()[0].url, url);
371 }
372 
373 #[test]
test_no_report_unrecognized_vendor_properties()374 fn test_no_report_unrecognized_vendor_properties() {
375     let css = r"
376     div {
377         -o-background-color: red;
378         _background-color: red;
379         -moz-background-color: red;
380     }
381     ";
382     let url = ServoUrl::parse("about::test").unwrap();
383     let error_reporter = TestingErrorReporter::new();
384 
385     let lock = SharedRwLock::new();
386     let media = Arc::new(lock.wrap(MediaList::empty()));
387     Stylesheet::from_str(css, url, Origin::UserAgent, media, lock,
388                          None, Some(&error_reporter), QuirksMode::NoQuirks, 0);
389 
390     error_reporter.assert_messages_contain(&[
391         (4, 31, "Unsupported property declaration: '-moz-background-color: red;'"),
392     ]);
393 }
394 
395 #[test]
test_source_map_url()396 fn test_source_map_url() {
397     let tests = vec![
398         ("", None),
399         ("/*# sourceMappingURL=something */", Some("something".to_string())),
400     ];
401 
402     for test in tests {
403         let url = ServoUrl::parse("about::test").unwrap();
404         let lock = SharedRwLock::new();
405         let media = Arc::new(lock.wrap(MediaList::empty()));
406         let stylesheet = Stylesheet::from_str(test.0, url.clone(), Origin::UserAgent, media, lock,
407                                               None, None, QuirksMode::NoQuirks,
408                                               0);
409         let url_opt = stylesheet.contents.source_map_url.read();
410         assert_eq!(*url_opt, test.1);
411     }
412 }
413 
414 #[test]
test_source_url()415 fn test_source_url() {
416     let tests = vec![
417         ("", None),
418         ("/*# sourceURL=something */", Some("something".to_string())),
419     ];
420 
421     for test in tests {
422         let url = ServoUrl::parse("about::test").unwrap();
423         let lock = SharedRwLock::new();
424         let media = Arc::new(lock.wrap(MediaList::empty()));
425         let stylesheet = Stylesheet::from_str(test.0, url.clone(), Origin::UserAgent, media, lock,
426                                               None, None, QuirksMode::NoQuirks,
427                                               0);
428         let url_opt = stylesheet.contents.source_url.read();
429         assert_eq!(*url_opt, test.1);
430     }
431 }
432