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