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