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