/* 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::{Parser, ParserInput}; use euclid::Scale; use euclid::Size2D; use servo_arc::Arc; use servo_config::prefs::{PREFS, PrefValue}; use servo_url::ServoUrl; use style::context::QuirksMode; use style::media_queries::{Device, MediaList, MediaType}; use style::parser::ParserContext; use style::shared_lock::{SharedRwLock, StylesheetGuards}; use style::stylesheets::{CssRuleType, Stylesheet, StylesheetInDocument, Origin}; use style::stylesheets::viewport_rule::*; use style::values::specified::LengthPercentageOrAuto::{self, Auto}; use style::values::specified::NoCalcLength::{self, ViewportPercentage}; use style::values::specified::ViewportPercentageLength::Vw; use style_traits::{ParsingMode, PinchZoomFactor}; use style_traits::viewport::*; macro_rules! stylesheet { ($css:expr, $origin:ident) => { stylesheet!($css, $origin, SharedRwLock::new()) }; ($css:expr, $origin:ident, $shared_lock:expr) => { Arc::new(Stylesheet::from_str( $css, ServoUrl::parse("http://localhost").unwrap(), Origin::$origin, Arc::new($shared_lock.wrap(MediaList::empty())), $shared_lock, None, None, QuirksMode::NoQuirks, 0 )) } } fn test_viewport_rule(css: &str, device: &Device, callback: F) where F: Fn(&Vec, &str) { PREFS.set("layout.viewport.enabled", PrefValue::Boolean(true)); let stylesheet = stylesheet!(css, Author); let mut rule_count = 0; stylesheet.effective_viewport_rules(&device, &stylesheet.shared_lock.read(), |rule| { rule_count += 1; callback(&rule.declarations, css); }); assert!(rule_count > 0); } fn test_meta_viewport(meta: &str, callback: F) where F: Fn(&Vec, &str) { if let Some(mut rule) = ViewportRule::from_meta(meta) { // from_meta uses a hash-map to collect the declarations, so we need to // sort them in a stable order for the tests rule.declarations.sort_by(|a, b| { let a = a.descriptor.discriminant_value(); let b = b.descriptor.discriminant_value(); a.cmp(&b) }); callback(&rule.declarations, meta); } else { panic!("no @viewport rule for {}", meta); } } macro_rules! assert_decl_len { ($declarations:ident == 1) => { assert_eq!($declarations.len(), 1, "expected 1 declaration; have {}: {:?})", $declarations.len(), $declarations) }; ($declarations:ident == $len:expr) => { assert_eq!($declarations.len(), $len, "expected {} declarations; have {}: {:?})", $len, $declarations.len(), $declarations) } } macro_rules! viewport_length { ($value:expr, px) => { ViewportLength::Specified(LengthPercentageOrAuto::Length(NoCalcLength::from_px($value))) }; ($value:expr, vw) => { ViewportLength::Specified(LengthPercentageOrAuto::Length(ViewportPercentage(Vw($value)))) } } #[test] fn empty_viewport_rule() { let device = Device::new(MediaType::screen(), Size2D::new(800., 600.), Scale::new(1.0)); test_viewport_rule("@viewport {}", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 0); }); } macro_rules! assert_decl_eq { ($d:expr, $origin:ident, $expected:ident: $value:expr) => {{ assert_eq!($d.origin, Origin::$origin); assert_eq!($d.descriptor, ViewportDescriptor::$expected($value)); assert_eq!($d.important, false, "descriptor should not be !important"); }}; ($d:expr, $origin:ident, $expected:ident: $value:expr, !important) => {{ assert_eq!($d.origin, Origin::$origin); assert_eq!($d.descriptor, ViewportDescriptor::$expected($value)); assert_eq!($d.important, true, "descriptor should be !important"); }}; } #[test] fn simple_viewport_rules() { let device = Device::new(MediaType::screen(), Size2D::new(800., 600.), Scale::new(1.0)); test_viewport_rule("@viewport { width: auto; height: auto;\ zoom: auto; min-zoom: 0; max-zoom: 200%;\ user-zoom: zoom; orientation: auto; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 9); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[2], Author, MinHeight: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[3], Author, MaxHeight: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[4], Author, Zoom: Zoom::Auto); assert_decl_eq!(&declarations[5], Author, MinZoom: Zoom::Number(0.)); assert_decl_eq!(&declarations[6], Author, MaxZoom: Zoom::Percentage(2.)); assert_decl_eq!(&declarations[7], Author, UserZoom: UserZoom::Zoom); assert_decl_eq!(&declarations[8], Author, Orientation: Orientation::Auto); }); test_viewport_rule("@viewport { min-width: 200px; max-width: auto;\ min-height: 200px; max-height: auto; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 4); assert_decl_eq!(&declarations[0], Author, MinWidth: viewport_length!(200., px)); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[2], Author, MinHeight: viewport_length!(200., px)); assert_decl_eq!(&declarations[3], Author, MaxHeight: ViewportLength::Specified(Auto)); }); } #[test] fn simple_meta_viewport_contents() { test_meta_viewport("width=500, height=600", |declarations, meta| { println!("{}", meta); assert_decl_len!(declarations == 4); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::ExtendToZoom); assert_decl_eq!(&declarations[1], Author, MaxWidth: viewport_length!(500., px)); assert_decl_eq!(&declarations[2], Author, MinHeight: ViewportLength::ExtendToZoom); assert_decl_eq!(&declarations[3], Author, MaxHeight: viewport_length!(600., px)); }); test_meta_viewport("initial-scale=1.0", |declarations, meta| { println!("{}", meta); assert_decl_len!(declarations == 3); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::ExtendToZoom); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::ExtendToZoom); assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(1.)); }); test_meta_viewport("initial-scale=2.0, height=device-width", |declarations, meta| { println!("{}", meta); assert_decl_len!(declarations == 5); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[2], Author, MinHeight: ViewportLength::ExtendToZoom); assert_decl_eq!(&declarations[3], Author, MaxHeight: viewport_length!(100., vw)); assert_decl_eq!(&declarations[4], Author, Zoom: Zoom::Number(2.)); }); test_meta_viewport("width=480, initial-scale=2.0, user-scalable=1", |declarations, meta| { println!("{}", meta); assert_decl_len!(declarations == 4); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::ExtendToZoom); assert_decl_eq!(&declarations[1], Author, MaxWidth: viewport_length!(480., px)); assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(2.)); assert_decl_eq!(&declarations[3], Author, UserZoom: UserZoom::Zoom); }); } #[test] fn cascading_within_viewport_rule() { let device = Device::new(MediaType::screen(), Size2D::new(800., 600.), Scale::new(1.0)); // normal order of appearance test_viewport_rule("@viewport { min-width: 200px; min-width: auto; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 1); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto)); }); // !important order of appearance test_viewport_rule("@viewport { min-width: 200px !important; min-width: auto !important; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 1); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important); }); // !important vs normal test_viewport_rule("@viewport { min-width: auto !important; min-width: 200px; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 1); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important); }); // normal longhands vs normal shorthand test_viewport_rule("@viewport { min-width: 200px; max-width: 200px; width: auto; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto)); }); // normal shorthand vs normal longhands test_viewport_rule("@viewport { width: 200px; min-width: auto; max-width: auto; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto)); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto)); }); // one !important longhand vs normal shorthand test_viewport_rule("@viewport { min-width: auto !important; width: 200px; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important); assert_decl_eq!(&declarations[1], Author, MaxWidth: viewport_length!(200., px)); }); // both !important longhands vs normal shorthand test_viewport_rule("@viewport { min-width: auto !important; max-width: auto !important; width: 200px; }", &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto), !important); }); } #[test] fn multiple_stylesheets_cascading() { PREFS.set("layout.viewport.enabled", PrefValue::Boolean(true)); let device = Device::new(MediaType::screen(), Size2D::new(800., 600.), Scale::new(1.0)); let shared_lock = SharedRwLock::new(); let stylesheets = vec![ stylesheet!("@viewport { min-width: 100px; min-height: 100px; zoom: 1; }", UserAgent, shared_lock.clone()), stylesheet!("@viewport { min-width: 200px; min-height: 200px; }", User, shared_lock.clone()), stylesheet!("@viewport { min-width: 300px; }", Author, shared_lock.clone()) ]; let declarations = Cascade::from_stylesheets( stylesheets.iter().map(|s| (&**s, Origin::Author)), &StylesheetGuards::same(&shared_lock.read()), &device, ).finish(); assert_decl_len!(declarations == 3); assert_decl_eq!(&declarations[0], UserAgent, Zoom: Zoom::Number(1.)); assert_decl_eq!(&declarations[1], User, MinHeight: viewport_length!(200., px)); assert_decl_eq!(&declarations[2], Author, MinWidth: viewport_length!(300., px)); let stylesheets = vec![ stylesheet!("@viewport { min-width: 100px !important; }", UserAgent, shared_lock.clone()), stylesheet!("@viewport { min-width: 200px !important; min-height: 200px !important; }", User, shared_lock.clone()), stylesheet!("@viewport { min-width: 300px !important; min-height: 300px !important; zoom: 3 !important; }", Author, shared_lock.clone()) ]; let declarations = Cascade::from_stylesheets( stylesheets.iter().map(|s| (&**s, Origin::Author)), &StylesheetGuards::same(&shared_lock.read()), &device, ).finish(); assert_decl_len!(declarations == 3); assert_decl_eq!(&declarations[0], UserAgent, MinWidth: viewport_length!(100., px), !important); assert_decl_eq!(&declarations[1], User, MinHeight: viewport_length!(200., px), !important); assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(3.), !important); } #[test] fn constrain_viewport() { let url = ServoUrl::parse("http://localhost").unwrap(); let context = ParserContext::new( Origin::Author, &url, Some(CssRuleType::Viewport), ParsingMode::DEFAULT, QuirksMode::NoQuirks, None, None, ); macro_rules! from_css { ($css:expr) => { &ViewportRule::parse(&context, &mut Parser::new(&mut $css)).unwrap() } } let initial_viewport = Size2D::new(800., 600.); let device = Device::new(MediaType::screen(), initial_viewport, Scale::new(1.0)); let mut input = ParserInput::new(""); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), None); let mut input = ParserInput::new("width: 320px auto"); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), Some(ViewportConstraints { size: initial_viewport, initial_zoom: PinchZoomFactor::new(1.), min_zoom: None, max_zoom: None, user_zoom: UserZoom::Zoom, orientation: Orientation::Auto })); let mut input = ParserInput::new("width: 320px auto"); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), Some(ViewportConstraints { size: initial_viewport, initial_zoom: PinchZoomFactor::new(1.), min_zoom: None, max_zoom: None, user_zoom: UserZoom::Zoom, orientation: Orientation::Auto })); let mut input = ParserInput::new("width: 800px; height: 600px;\ zoom: 1;\ user-zoom: zoom;\ orientation: auto;"); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), Some(ViewportConstraints { size: initial_viewport, initial_zoom: PinchZoomFactor::new(1.), min_zoom: None, max_zoom: None, user_zoom: UserZoom::Zoom, orientation: Orientation::Auto })); let initial_viewport = Size2D::new(200., 150.); let device = Device::new(MediaType::screen(), initial_viewport, Scale::new(1.0)); let mut input = ParserInput::new("width: 320px auto"); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), Some(ViewportConstraints { size: Size2D::new(320., 240.), initial_zoom: PinchZoomFactor::new(1.), min_zoom: None, max_zoom: None, user_zoom: UserZoom::Zoom, orientation: Orientation::Auto })); }