/* 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::SourceLocation; use euclid::{Scale, Size2D}; use selectors::parser::{AncestorHashes, Selector}; use servo_arc::Arc; use style::context::QuirksMode; use style::font_metrics::FontMetrics; use style::media_queries::{Device, MediaType}; use style::properties::style_structs::Font; use style::properties::{ ComputedValues, Importance, PropertyDeclaration, PropertyDeclarationBlock, longhands, }; use style::queries::values::PrefersColorScheme; use style::rule_tree::StyleSource; use style::selector_map::SelectorMap; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::servo::media_queries::FontMetricsProvider; use style::shared_lock::SharedRwLock; use style::stylesheets::StyleRule; use style::stylist::{ ContainerConditionId, LayerId, Rule, ScopeConditionId, Stylist, needs_revalidation_for_testing, }; use style::thread_state::{self, ThreadState}; use style::values::computed::Length; use style::values::computed::font::GenericFontFamily; use stylo_atoms::Atom; use url::Url; #[derive(Debug)] struct DummyMetricsProvider; impl FontMetricsProvider for DummyMetricsProvider { fn query_font_metrics( &self, _vertical: bool, _font: &Font, _base_size: Length, _in_media_query: bool, _retrieve_math_scales: bool, ) -> FontMetrics { Default::default() } fn base_size_for_generic(&self, _: GenericFontFamily) -> Length { Length::new(16.) } } /// Helper method to get some Rules from selector strings. /// Each sublist of the result contains the Rules for one StyleRule. fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { let dummy_url_data = Url::parse("about:blank").unwrap().into(); let shared_lock = SharedRwLock::new(); ( css_selectors .iter() .enumerate() .map(|(i, selectors)| { let selectors = SelectorParser::parse_author_origin_no_namespace(selectors, &dummy_url_data) .unwrap(); let locked = Arc::new(shared_lock.wrap(StyleRule { selectors, block: Arc::new(shared_lock.wrap(PropertyDeclarationBlock::with_one( PropertyDeclaration::Display(longhands::display::SpecifiedValue::Block), Importance::Normal, ))), rules: None, source_location: SourceLocation { line: 0, column: 0 }, })); let guard = shared_lock.read(); let rule = locked.read_with(&guard); rule.selectors .slice() .iter() .map(|s| { Rule::new( s.clone(), AncestorHashes::new(s, QuirksMode::NoQuirks), StyleSource::from_declarations(rule.block.clone()), i as u32, LayerId::root(), ContainerConditionId::none(), /* in_starting_style = */ false, ScopeConditionId::none(), ) }) .collect() }) .collect(), shared_lock, ) } fn parse_selectors(selectors: &[&str]) -> Vec> { let dummy_url_data = Url::parse("about:blank").unwrap().into(); selectors .iter() .map(|x| { SelectorParser::parse_author_origin_no_namespace(x, &dummy_url_data) .unwrap() .slice() .into_iter() .next() .unwrap() .clone() }) .collect() } #[test] fn test_revalidation_selectors() { let test = parse_selectors(&[ // Not revalidation selectors. "div", "div:not(.foo)", "div span", "div > span", // ID selectors. "#foo1", "#foo2::before", "#foo3 > span", "#foo1 > span", // FIXME(bz): This one should not be a // revalidation selector, since #foo1 should be in the // rule hash. // Attribute selectors. "div[foo]", "div:not([foo])", "div[foo = \"bar\"]", "div[foo ~= \"bar\"]", "div[foo |= \"bar\"]", "div[foo ^= \"bar\"]", "div[foo $= \"bar\"]", "div[foo *= \"bar\"]", "*|div[foo][bar = \"baz\"]", // Non-state-based pseudo-classes. "div:empty", "div:first-child", "div:last-child", "div:only-child", "div:nth-child(2)", "div:nth-last-child(2)", "div:nth-of-type(2)", "div:nth-last-of-type(2)", "div:first-of-type", "div:last-of-type", "div:only-of-type", // Note: it would be nice to test :moz-any and the various other non-TS // pseudo classes supported by gecko, but we don't have access to those // in these unit tests. :-( // Sibling combinators. "span + div", "span ~ div", // Selectors in the ancestor chain (needed for cousin sharing). "p:first-child span", ]) .into_iter() .filter(needs_revalidation_for_testing) .collect::>(); let reference = parse_selectors(&[ // ID selectors. "#foo3 > span", "#foo1 > span", // Attribute selectors. "div[foo]", "div:not([foo])", "div[foo = \"bar\"]", "div[foo ~= \"bar\"]", "div[foo |= \"bar\"]", "div[foo ^= \"bar\"]", "div[foo $= \"bar\"]", "div[foo *= \"bar\"]", "*|div[foo][bar = \"baz\"]", // Non-state-based pseudo-classes. "div:empty", "div:first-child", "div:last-child", "div:only-child", "div:nth-child(2)", "div:nth-last-child(2)", "div:nth-of-type(2)", "div:nth-last-of-type(2)", "div:first-of-type", "div:last-of-type", "div:only-of-type", // Sibling combinators. "span + div", "span ~ div", // Selectors in the ancestor chain (needed for cousin sharing). "p:first-child span", ]) .into_iter() .collect::>(); assert_eq!(test.len(), reference.len()); for (t, r) in test.into_iter().zip(reference.into_iter()) { assert_eq!(t, r) } } #[test] fn test_rule_ordering_same_specificity() { let (rules_list, _) = get_mock_rules(&["a.intro", "img.sidebar"]); let a = &rules_list[0][0]; let b = &rules_list[1][0]; assert!( (a.specificity(), a.source_order) < ((b.specificity(), b.source_order)), "The rule that comes later should win." ); } #[test] fn test_insert() { let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]); let mut selector_map = SelectorMap::new(); selector_map .insert(rules_list[1][0].clone(), QuirksMode::NoQuirks) .expect("OOM"); assert_eq!( 1, selector_map .id_hash .get(&Atom::from("top"), QuirksMode::NoQuirks) .unwrap()[0] .source_order ); selector_map .insert(rules_list[0][0].clone(), QuirksMode::NoQuirks) .expect("OOM"); assert_eq!( 0, selector_map .class_hash .get(&Atom::from("foo"), QuirksMode::NoQuirks) .unwrap()[0] .source_order ); assert!( selector_map .class_hash .get(&Atom::from("intro"), QuirksMode::NoQuirks) .is_none() ); } fn mock_stylist() -> Stylist { let initial_style = ComputedValues::initial_values_with_font_override(Font::initial_values()); let device = Device::new( MediaType::screen(), QuirksMode::NoQuirks, Size2D::new(0f32, 0f32), Scale::new(1.0), Box::new(DummyMetricsProvider), initial_style, PrefersColorScheme::Light, ); Stylist::new(device, QuirksMode::NoQuirks) } #[test] fn test_stylist_device_accessors() { thread_state::initialize(ThreadState::LAYOUT); let stylist = mock_stylist(); assert_eq!(stylist.device().media_type(), MediaType::screen()); let mut stylist_mut = mock_stylist(); assert_eq!(stylist_mut.device_mut().media_type(), MediaType::screen()); } #[test] fn test_stylist_rule_tree_accessors() { thread_state::initialize(ThreadState::LAYOUT); let stylist = mock_stylist(); stylist.rule_tree(); stylist.rule_tree().root(); }