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