diff options
Diffstat (limited to 'servo/tests/unit/style/stylesheets.rs')
-rw-r--r-- | servo/tests/unit/style/stylesheets.rs | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/servo/tests/unit/style/stylesheets.rs b/servo/tests/unit/style/stylesheets.rs new file mode 100644 index 0000000000..8caed3db27 --- /dev/null +++ b/servo/tests/unit/style/stylesheets.rs @@ -0,0 +1,564 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use cssparser::{self, SourceLocation}; +use html5ever::Namespace as NsAtom; +use parking_lot::RwLock; +use selectors::attr::*; +use selectors::parser::*; +use servo_arc::Arc; +use servo_atoms::Atom; +use servo_config::prefs::{PrefValue, PREFS}; +use servo_url::ServoUrl; +use std::borrow::ToOwned; +use std::cell::RefCell; +use std::sync::atomic::AtomicBool; +use style::context::QuirksMode; +use style::error_reporting::{ContextualParseError, ParseErrorReporter}; +use style::media_queries::MediaList; +use style::properties::longhands::{self, animation_timing_function}; +use style::properties::{CSSWideKeyword, CustomDeclaration}; +use style::properties::{CustomDeclarationValue, Importance}; +use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; +use style::shared_lock::SharedRwLock; +use style::stylesheets::keyframes_rule::{Keyframe, KeyframePercentage, KeyframeSelector}; +use style::stylesheets::{ + CssRule, CssRules, KeyframesRule, NamespaceRule, StyleRule, Stylesheet, StylesheetContents, +}; +use style::stylesheets::{Namespaces, Origin}; +use style::values::computed::Percentage; +use style::values::specified::TimingFunction; +use style::values::specified::{LengthPercentageOrAuto, PositionComponent}; +use style::values::{CustomIdent, KeyframesName}; + +pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock +where + I: IntoIterator<Item = (PropertyDeclaration, Importance)>, +{ + let mut block = PropertyDeclarationBlock::new(); + for (d, i) in iterable { + block.push(d, i); + } + block +} + +#[test] +fn test_parse_stylesheet() { + let css = r" + @namespace url(http://www.w3.org/1999/xhtml); + /* FIXME: only if scripting is enabled */ + input[type=hidden i] { + display: block !important; + display: none !important; + display: inline; + --a: b !important; + --a: inherit !important; + --a: c; + } + html , body /**/ { + display: none; + display: block; + } + #d1 > .ok { background: blue; } + @keyframes foo { + from { width: 0% } + to { + width: 100%; + width: 50% !important; /* !important not allowed here */ + animation-name: 'foo'; /* animation properties not allowed here */ + animation-timing-function: ease; /* … except animation-timing-function */ + } + }"; + let url = ServoUrl::parse("about::test").unwrap(); + let lock = SharedRwLock::new(); + let media = Arc::new(lock.wrap(MediaList::empty())); + let stylesheet = Stylesheet::from_str( + css, + url.clone(), + Origin::UserAgent, + media, + lock, + None, + None, + QuirksMode::NoQuirks, + 0, + ); + let mut namespaces = Namespaces::default(); + namespaces.default = Some(ns!(html)); + let expected = Stylesheet { + contents: StylesheetContents { + origin: Origin::UserAgent, + namespaces: RwLock::new(namespaces), + url_data: RwLock::new(url), + quirks_mode: QuirksMode::NoQuirks, + rules: CssRules::new( + vec![ + CssRule::Namespace(Arc::new(stylesheet.shared_lock.wrap(NamespaceRule { + prefix: None, + url: NsAtom::from("http://www.w3.org/1999/xhtml"), + source_location: SourceLocation { + line: 1, + column: 19, + }, + }))), + CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { + selectors: SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(NsAtom::from( + "http://www.w3.org/1999/xhtml", + )), + Component::LocalName(LocalName { + name: local_name!("input"), + lower_name: local_name!("input"), + }), + Component::AttributeInNoNamespace { + local_name: local_name!("type"), + operator: AttrSelectorOperator::Equal, + value: "hidden".to_owned(), + case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive, + never_matches: false, + }, + ], + (0 << 20) + (1 << 10) + (1 << 0), + )]), + block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ + ( + PropertyDeclaration::Display( + longhands::display::SpecifiedValue::None, + ), + Importance::Important, + ), + ( + PropertyDeclaration::Custom(CustomDeclaration { + name: Atom::from("a"), + value: CustomDeclarationValue::CSSWideKeyword( + CSSWideKeyword::Inherit, + ), + }), + Importance::Important, + ), + ]))), + source_location: SourceLocation { line: 3, column: 9 }, + }))), + CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { + selectors: SelectorList::from_vec(vec![ + Selector::from_vec( + vec![ + Component::DefaultNamespace(NsAtom::from( + "http://www.w3.org/1999/xhtml", + )), + Component::LocalName(LocalName { + name: local_name!("html"), + lower_name: local_name!("html"), + }), + ], + (0 << 20) + (0 << 10) + (1 << 0), + ), + Selector::from_vec( + vec![ + Component::DefaultNamespace(NsAtom::from( + "http://www.w3.org/1999/xhtml", + )), + Component::LocalName(LocalName { + name: local_name!("body"), + lower_name: local_name!("body"), + }), + ], + (0 << 20) + (0 << 10) + (1 << 0), + ), + ]), + block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![( + PropertyDeclaration::Display(longhands::display::SpecifiedValue::Block), + Importance::Normal, + )]))), + source_location: SourceLocation { + line: 11, + column: 9, + }, + }))), + CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { + selectors: SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(NsAtom::from( + "http://www.w3.org/1999/xhtml", + )), + Component::ID(Atom::from("d1")), + Component::Combinator(Combinator::Child), + Component::DefaultNamespace(NsAtom::from( + "http://www.w3.org/1999/xhtml", + )), + Component::Class(Atom::from("ok")), + ], + (1 << 20) + (1 << 10) + (0 << 0), + )]), + block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ + ( + PropertyDeclaration::BackgroundColor( + longhands::background_color::SpecifiedValue::Numeric { + authored: Some("blue".to_owned().into_boxed_str()), + parsed: cssparser::RGBA::new(0, 0, 255, 255), + }, + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundPositionX( + longhands::background_position_x::SpecifiedValue(vec![ + PositionComponent::zero(), + ]), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundPositionY( + longhands::background_position_y::SpecifiedValue(vec![ + PositionComponent::zero(), + ]), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundRepeat( + longhands::background_repeat::SpecifiedValue( + vec![longhands::background_repeat::single_value + ::get_initial_specified_value()], + ), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundAttachment( + longhands::background_attachment::SpecifiedValue( + vec![longhands::background_attachment::single_value + ::get_initial_specified_value()], + ), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundImage( + longhands::background_image::SpecifiedValue( + vec![longhands::background_image::single_value + ::get_initial_specified_value()], + ), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundSize( + longhands::background_size::SpecifiedValue( + vec![longhands::background_size::single_value + ::get_initial_specified_value()], + ), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundOrigin( + longhands::background_origin::SpecifiedValue( + vec![longhands::background_origin::single_value + ::get_initial_specified_value()], + ), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::BackgroundClip( + longhands::background_clip::SpecifiedValue( + vec![longhands::background_clip::single_value + ::get_initial_specified_value()], + ), + ), + Importance::Normal, + ), + ]))), + source_location: SourceLocation { + line: 15, + column: 9, + }, + }))), + CssRule::Keyframes(Arc::new(stylesheet.shared_lock.wrap(KeyframesRule { + name: KeyframesName::Ident(CustomIdent("foo".into())), + keyframes: vec![ + Arc::new(stylesheet.shared_lock.wrap(Keyframe { + selector: KeyframeSelector::new_for_unit_testing(vec![ + KeyframePercentage::new(0.), + ]), + block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![( + PropertyDeclaration::Width(LengthPercentageOrAuto::Percentage( + Percentage(0.), + )), + Importance::Normal, + )]))), + source_location: SourceLocation { + line: 17, + column: 13, + }, + })), + Arc::new(stylesheet.shared_lock.wrap(Keyframe { + selector: KeyframeSelector::new_for_unit_testing(vec![ + KeyframePercentage::new(1.), + ]), + block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ + ( + PropertyDeclaration::Width( + LengthPercentageOrAuto::Percentage(Percentage(1.)), + ), + Importance::Normal, + ), + ( + PropertyDeclaration::AnimationTimingFunction( + animation_timing_function::SpecifiedValue(vec![ + TimingFunction::ease(), + ]), + ), + Importance::Normal, + ), + ]))), + source_location: SourceLocation { + line: 18, + column: 13, + }, + })), + ], + vendor_prefix: None, + source_location: SourceLocation { + line: 16, + column: 19, + }, + }))), + ], + &stylesheet.shared_lock, + ), + source_map_url: RwLock::new(None), + source_url: RwLock::new(None), + }, + media: Arc::new(stylesheet.shared_lock.wrap(MediaList::empty())), + shared_lock: stylesheet.shared_lock.clone(), + disabled: AtomicBool::new(false), + }; + + assert_eq!(format!("{:#?}", stylesheet), format!("{:#?}", expected)); +} + +#[derive(Debug)] +struct CSSError { + pub url: ServoUrl, + pub line: u32, + pub column: u32, + pub message: String, +} + +struct TestingErrorReporter { + errors: RefCell<Vec<CSSError>>, +} + +impl TestingErrorReporter { + pub fn new() -> Self { + TestingErrorReporter { + errors: RefCell::new(Vec::new()), + } + } + + fn assert_messages_contain(&self, expected_errors: &[(u32, u32, &str)]) { + let errors = self.errors.borrow(); + for (i, (error, &(line, column, message))) in errors.iter().zip(expected_errors).enumerate() + { + assert_eq!( + (error.line, error.column), + (line, column), + "line/column numbers of the {}th error: {:?}", + i + 1, + error.message + ); + assert!( + error.message.contains(message), + "{:?} does not contain {:?}", + error.message, + message + ); + } + if errors.len() < expected_errors.len() { + panic!("Missing errors: {:#?}", &expected_errors[errors.len()..]); + } + if errors.len() > expected_errors.len() { + panic!("Extra errors: {:#?}", &errors[expected_errors.len()..]); + } + } +} + +impl ParseErrorReporter for TestingErrorReporter { + fn report_error(&self, url: &ServoUrl, location: SourceLocation, error: ContextualParseError) { + self.errors.borrow_mut().push(CSSError { + url: url.clone(), + line: location.line, + column: location.column, + message: error.to_string(), + }) + } +} + +#[test] +fn test_report_error_stylesheet() { + PREFS.set("layout.viewport.enabled", PrefValue::Boolean(true)); + let css = r" + div { + background-color: red; + display: invalid; + background-image: linear-gradient(0deg, black, invalid, transparent); + invalid: true; + } + @media (min-width: 10px invalid 1000px) {} + @font-face { src: url(), invalid, url(); } + @counter-style foo { symbols: a 0invalid b } + @font-feature-values Sans Sans { @foo {} @swash { foo: 1 invalid 2 } } + @invalid; + @media screen { @invalid; } + @supports (color: green) and invalid and (margin: 0) {} + @keyframes foo { from invalid {} to { margin: 0 invalid 0; } } + @viewport { width: 320px invalid auto; } + "; + let url = ServoUrl::parse("about::test").unwrap(); + let error_reporter = TestingErrorReporter::new(); + + let lock = SharedRwLock::new(); + let media = Arc::new(lock.wrap(MediaList::empty())); + Stylesheet::from_str( + css, + url.clone(), + Origin::UserAgent, + media, + lock, + None, + Some(&error_reporter), + QuirksMode::NoQuirks, + 5, + ); + + error_reporter.assert_messages_contain(&[ + ( + 8, + 18, + "Unsupported property declaration: 'display: invalid;'", + ), + ( + 9, + 27, + "Unsupported property declaration: 'background-image:", + ), // FIXME: column should be around 56 + (10, 17, "Unsupported property declaration: 'invalid: true;'"), + (12, 28, "Invalid media rule"), + (13, 30, "Unsupported @font-face descriptor declaration"), + // When @counter-style is supported, this should be replaced with two errors + (14, 19, "Invalid rule: '@counter-style "), + // When @font-feature-values is supported, this should be replaced with two errors + (15, 25, "Invalid rule: '@font-feature-values "), + (16, 13, "Invalid rule: '@invalid'"), + (17, 29, "Invalid rule: '@invalid'"), + (18, 34, "Invalid rule: '@supports "), + (19, 26, "Invalid keyframe rule: 'from invalid '"), + ( + 19, + 52, + "Unsupported keyframe property declaration: 'margin: 0 invalid 0;'", + ), + ( + 20, + 29, + "Unsupported @viewport descriptor declaration: 'width: 320px invalid auto;'", + ), + ]); + + assert_eq!(error_reporter.errors.borrow()[0].url, url); +} + +#[test] +fn test_no_report_unrecognized_vendor_properties() { + let css = r" + div { + -o-background-color: red; + _background-color: red; + -moz-background-color: red; + } + "; + let url = ServoUrl::parse("about::test").unwrap(); + let error_reporter = TestingErrorReporter::new(); + + let lock = SharedRwLock::new(); + let media = Arc::new(lock.wrap(MediaList::empty())); + Stylesheet::from_str( + css, + url, + Origin::UserAgent, + media, + lock, + None, + Some(&error_reporter), + QuirksMode::NoQuirks, + 0, + ); + + error_reporter.assert_messages_contain(&[( + 4, + 31, + "Unsupported property declaration: '-moz-background-color: red;'", + )]); +} + +#[test] +fn test_source_map_url() { + let tests = vec![ + ("", None), + ( + "/*# sourceMappingURL=something */", + Some("something".to_string()), + ), + ]; + + for test in tests { + let url = ServoUrl::parse("about::test").unwrap(); + let lock = SharedRwLock::new(); + let media = Arc::new(lock.wrap(MediaList::empty())); + let stylesheet = Stylesheet::from_str( + test.0, + url.clone(), + Origin::UserAgent, + media, + lock, + None, + None, + QuirksMode::NoQuirks, + 0, + ); + let url_opt = stylesheet.contents.source_map_url.read(); + assert_eq!(*url_opt, test.1); + } +} + +#[test] +fn test_source_url() { + let tests = vec![ + ("", None), + ("/*# sourceURL=something */", Some("something".to_string())), + ]; + + for test in tests { + let url = ServoUrl::parse("about::test").unwrap(); + let lock = SharedRwLock::new(); + let media = Arc::new(lock.wrap(MediaList::empty())); + let stylesheet = Stylesheet::from_str( + test.0, + url.clone(), + Origin::UserAgent, + media, + lock, + None, + None, + QuirksMode::NoQuirks, + 0, + ); + let url_opt = stylesheet.contents.source_url.read(); + assert_eq!(*url_opt, test.1); + } +} |