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 http://mozilla.org/MPL/2.0/. */
4 
5 use cssparser::{Parser, ParserInput};
6 use euclid::TypedScale;
7 use euclid::TypedSize2D;
8 use media_queries::CSSErrorReporterTest;
9 use servo_arc::Arc;
10 use servo_config::prefs::{PREFS, PrefValue};
11 use servo_url::ServoUrl;
12 use style::context::QuirksMode;
13 use style::media_queries::{Device, MediaList, MediaType};
14 use style::parser::{ParserContext, ParserErrorContext};
15 use style::shared_lock::{SharedRwLock, StylesheetGuards};
16 use style::stylesheets::{CssRuleType, Stylesheet, StylesheetInDocument, Origin};
17 use style::stylesheets::viewport_rule::*;
18 use style::values::specified::LengthOrPercentageOrAuto::{self, Auto};
19 use style::values::specified::NoCalcLength::{self, ViewportPercentage};
20 use style::values::specified::ViewportPercentageLength::Vw;
21 use style_traits::{ParsingMode, PinchZoomFactor};
22 use style_traits::viewport::*;
23 
24 macro_rules! stylesheet {
25     ($css:expr, $origin:ident, $error_reporter:expr) => {
26         stylesheet!($css, $origin, $error_reporter, SharedRwLock::new())
27     };
28     ($css:expr, $origin:ident, $error_reporter:expr, $shared_lock:expr) => {
29         Arc::new(Stylesheet::from_str(
30             $css,
31             ServoUrl::parse("http://localhost").unwrap(),
32             Origin::$origin,
33             Arc::new($shared_lock.wrap(MediaList::empty())),
34             $shared_lock,
35             None,
36             &$error_reporter,
37             QuirksMode::NoQuirks,
38             0
39         ))
40     }
41 }
42 
test_viewport_rule<F>(css: &str, device: &Device, callback: F) where F: Fn(&Vec<ViewportDescriptorDeclaration>, &str)43 fn test_viewport_rule<F>(css: &str,
44                          device: &Device,
45                          callback: F)
46     where F: Fn(&Vec<ViewportDescriptorDeclaration>, &str)
47 {
48     PREFS.set("layout.viewport.enabled", PrefValue::Boolean(true));
49     let stylesheet = stylesheet!(css, Author, CSSErrorReporterTest);
50     let mut rule_count = 0;
51     stylesheet.effective_viewport_rules(&device, &stylesheet.shared_lock.read(), |rule| {
52         rule_count += 1;
53         callback(&rule.declarations, css);
54     });
55     assert!(rule_count > 0);
56 }
57 
test_meta_viewport<F>(meta: &str, callback: F) where F: Fn(&Vec<ViewportDescriptorDeclaration>, &str)58 fn test_meta_viewport<F>(meta: &str, callback: F)
59     where F: Fn(&Vec<ViewportDescriptorDeclaration>, &str)
60 {
61     if let Some(mut rule) = ViewportRule::from_meta(meta) {
62         // from_meta uses a hash-map to collect the declarations, so we need to
63         // sort them in a stable order for the tests
64         rule.declarations.sort_by(|a, b| {
65             let a = a.descriptor.discriminant_value();
66             let b = b.descriptor.discriminant_value();
67             a.cmp(&b)
68         });
69 
70         callback(&rule.declarations, meta);
71     } else {
72         panic!("no @viewport rule for {}", meta);
73     }
74 }
75 
76 macro_rules! assert_decl_len {
77     ($declarations:ident == 1) => {
78         assert_eq!($declarations.len(), 1,
79                 "expected 1 declaration; have {}: {:?})",
80                 $declarations.len(), $declarations)
81     };
82     ($declarations:ident == $len:expr) => {
83         assert_eq!($declarations.len(), $len,
84                 "expected {} declarations; have {}: {:?})",
85                 $len, $declarations.len(), $declarations)
86     }
87 }
88 
89 macro_rules! viewport_length {
90     ($value:expr, px) => {
91         ViewportLength::Specified(LengthOrPercentageOrAuto::Length(NoCalcLength::from_px($value)))
92     };
93     ($value:expr, vw) => {
94         ViewportLength::Specified(LengthOrPercentageOrAuto::Length(ViewportPercentage(Vw($value))))
95     }
96 }
97 
98 #[test]
empty_viewport_rule()99 fn empty_viewport_rule() {
100     let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), TypedScale::new(1.0));
101 
102     test_viewport_rule("@viewport {}", &device, |declarations, css| {
103         println!("{}", css);
104         assert_decl_len!(declarations == 0);
105     });
106 }
107 
108 macro_rules! assert_decl_eq {
109     ($d:expr, $origin:ident, $expected:ident: $value:expr) => {{
110         assert_eq!($d.origin, Origin::$origin);
111         assert_eq!($d.descriptor, ViewportDescriptor::$expected($value));
112         assert_eq!($d.important, false, "descriptor should not be !important");
113     }};
114     ($d:expr, $origin:ident, $expected:ident: $value:expr, !important) => {{
115         assert_eq!($d.origin, Origin::$origin);
116         assert_eq!($d.descriptor, ViewportDescriptor::$expected($value));
117         assert_eq!($d.important, true, "descriptor should be !important");
118     }};
119 }
120 
121 #[test]
simple_viewport_rules()122 fn simple_viewport_rules() {
123     let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), TypedScale::new(1.0));
124 
125     test_viewport_rule("@viewport { width: auto; height: auto;\
126                                     zoom: auto; min-zoom: 0; max-zoom: 200%;\
127                                     user-zoom: zoom; orientation: auto; }",
128                        &device, |declarations, css| {
129         println!("{}", css);
130         assert_decl_len!(declarations == 9);
131         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
132         assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
133         assert_decl_eq!(&declarations[2], Author, MinHeight: ViewportLength::Specified(Auto));
134         assert_decl_eq!(&declarations[3], Author, MaxHeight: ViewportLength::Specified(Auto));
135         assert_decl_eq!(&declarations[4], Author, Zoom: Zoom::Auto);
136         assert_decl_eq!(&declarations[5], Author, MinZoom: Zoom::Number(0.));
137         assert_decl_eq!(&declarations[6], Author, MaxZoom: Zoom::Percentage(2.));
138         assert_decl_eq!(&declarations[7], Author, UserZoom: UserZoom::Zoom);
139         assert_decl_eq!(&declarations[8], Author, Orientation: Orientation::Auto);
140     });
141 
142     test_viewport_rule("@viewport { min-width: 200px; max-width: auto;\
143                                     min-height: 200px; max-height: auto; }",
144                        &device, |declarations, css| {
145         println!("{}", css);
146         assert_decl_len!(declarations == 4);
147         assert_decl_eq!(&declarations[0], Author, MinWidth: viewport_length!(200., px));
148         assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
149         assert_decl_eq!(&declarations[2], Author, MinHeight: viewport_length!(200., px));
150         assert_decl_eq!(&declarations[3], Author, MaxHeight: ViewportLength::Specified(Auto));
151     });
152 }
153 
154 #[test]
simple_meta_viewport_contents()155 fn simple_meta_viewport_contents() {
156     test_meta_viewport("width=500, height=600", |declarations, meta| {
157         println!("{}", meta);
158         assert_decl_len!(declarations == 4);
159         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::ExtendToZoom);
160         assert_decl_eq!(&declarations[1], Author, MaxWidth: viewport_length!(500., px));
161         assert_decl_eq!(&declarations[2], Author, MinHeight: ViewportLength::ExtendToZoom);
162         assert_decl_eq!(&declarations[3], Author, MaxHeight: viewport_length!(600., px));
163     });
164 
165     test_meta_viewport("initial-scale=1.0", |declarations, meta| {
166         println!("{}", meta);
167         assert_decl_len!(declarations == 3);
168         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::ExtendToZoom);
169         assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::ExtendToZoom);
170         assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(1.));
171     });
172 
173     test_meta_viewport("initial-scale=2.0, height=device-width", |declarations, meta| {
174         println!("{}", meta);
175         assert_decl_len!(declarations == 5);
176         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
177         assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
178         assert_decl_eq!(&declarations[2], Author, MinHeight: ViewportLength::ExtendToZoom);
179         assert_decl_eq!(&declarations[3], Author, MaxHeight: viewport_length!(100., vw));
180         assert_decl_eq!(&declarations[4], Author, Zoom: Zoom::Number(2.));
181     });
182 
183     test_meta_viewport("width=480, initial-scale=2.0, user-scalable=1", |declarations, meta| {
184         println!("{}", meta);
185         assert_decl_len!(declarations == 4);
186         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::ExtendToZoom);
187         assert_decl_eq!(&declarations[1], Author, MaxWidth: viewport_length!(480., px));
188         assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(2.));
189         assert_decl_eq!(&declarations[3], Author, UserZoom: UserZoom::Zoom);
190     });
191 }
192 
193 #[test]
cascading_within_viewport_rule()194 fn cascading_within_viewport_rule() {
195     let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), TypedScale::new(1.0));
196 
197     // normal order of appearance
198     test_viewport_rule("@viewport { min-width: 200px; min-width: auto; }",
199                        &device, |declarations, css| {
200         println!("{}", css);
201         assert_decl_len!(declarations == 1);
202         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
203     });
204 
205     // !important order of appearance
206     test_viewport_rule("@viewport { min-width: 200px !important; min-width: auto !important; }",
207                        &device, |declarations, css| {
208         println!("{}", css);
209         assert_decl_len!(declarations == 1);
210         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
211     });
212 
213     // !important vs normal
214     test_viewport_rule("@viewport { min-width: auto !important; min-width: 200px; }",
215                        &device, |declarations, css| {
216         println!("{}", css);
217         assert_decl_len!(declarations == 1);
218         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
219     });
220 
221     // normal longhands vs normal shorthand
222     test_viewport_rule("@viewport { min-width: 200px; max-width: 200px; width: auto; }",
223                        &device, |declarations, css| {
224         println!("{}", css);
225         assert_decl_len!(declarations == 2);
226         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
227         assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
228     });
229 
230     // normal shorthand vs normal longhands
231     test_viewport_rule("@viewport { width: 200px; min-width: auto; max-width: auto; }",
232                        &device, |declarations, css| {
233         println!("{}", css);
234         assert_decl_len!(declarations == 2);
235         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
236         assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
237     });
238 
239     // one !important longhand vs normal shorthand
240     test_viewport_rule("@viewport { min-width: auto !important; width: 200px; }",
241                        &device, |declarations, css| {
242         println!("{}", css);
243         assert_decl_len!(declarations == 2);
244         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
245         assert_decl_eq!(&declarations[1], Author, MaxWidth: viewport_length!(200., px));
246     });
247 
248     // both !important longhands vs normal shorthand
249     test_viewport_rule("@viewport { min-width: auto !important; max-width: auto !important; width: 200px; }",
250                        &device, |declarations, css| {
251         println!("{}", css);
252         assert_decl_len!(declarations == 2);
253         assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
254         assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto), !important);
255     });
256 }
257 
258 #[test]
multiple_stylesheets_cascading()259 fn multiple_stylesheets_cascading() {
260     PREFS.set("layout.viewport.enabled", PrefValue::Boolean(true));
261     let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), TypedScale::new(1.0));
262     let error_reporter = CSSErrorReporterTest;
263     let shared_lock = SharedRwLock::new();
264     let stylesheets = vec![
265         stylesheet!("@viewport { min-width: 100px; min-height: 100px; zoom: 1; }",
266                     UserAgent, error_reporter, shared_lock.clone()),
267         stylesheet!("@viewport { min-width: 200px; min-height: 200px; }",
268                     User, error_reporter, shared_lock.clone()),
269         stylesheet!("@viewport { min-width: 300px; }",
270                     Author, error_reporter, shared_lock.clone())
271     ];
272 
273     let declarations = Cascade::from_stylesheets(
274         stylesheets.iter().map(|s| (&**s, Origin::Author)),
275         &StylesheetGuards::same(&shared_lock.read()),
276         &device,
277     ).finish();
278     assert_decl_len!(declarations == 3);
279     assert_decl_eq!(&declarations[0], UserAgent, Zoom: Zoom::Number(1.));
280     assert_decl_eq!(&declarations[1], User, MinHeight: viewport_length!(200., px));
281     assert_decl_eq!(&declarations[2], Author, MinWidth: viewport_length!(300., px));
282 
283     let stylesheets = vec![
284         stylesheet!("@viewport { min-width: 100px !important; }",
285                     UserAgent, error_reporter, shared_lock.clone()),
286         stylesheet!("@viewport { min-width: 200px !important; min-height: 200px !important; }",
287                     User, error_reporter, shared_lock.clone()),
288         stylesheet!("@viewport { min-width: 300px !important; min-height: 300px !important; zoom: 3 !important; }",
289                     Author, error_reporter, shared_lock.clone())
290     ];
291     let declarations = Cascade::from_stylesheets(
292         stylesheets.iter().map(|s| (&**s, Origin::Author)),
293         &StylesheetGuards::same(&shared_lock.read()),
294         &device,
295     ).finish();
296     assert_decl_len!(declarations == 3);
297     assert_decl_eq!(&declarations[0], UserAgent, MinWidth: viewport_length!(100., px), !important);
298     assert_decl_eq!(&declarations[1], User, MinHeight: viewport_length!(200., px), !important);
299     assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(3.), !important);
300 }
301 
302 #[test]
constrain_viewport()303 fn constrain_viewport() {
304     let url = ServoUrl::parse("http://localhost").unwrap();
305     let context = ParserContext::new(Origin::Author, &url, Some(CssRuleType::Viewport),
306                                      ParsingMode::DEFAULT,
307                                      QuirksMode::NoQuirks);
308     let error_context = ParserErrorContext { error_reporter: &CSSErrorReporterTest };
309 
310     macro_rules! from_css {
311         ($css:expr) => {
312             &ViewportRule::parse(&context, &error_context, &mut Parser::new(&mut $css)).unwrap()
313         }
314     }
315 
316     let initial_viewport = TypedSize2D::new(800., 600.);
317     let device = Device::new(MediaType::screen(), initial_viewport, TypedScale::new(1.0));
318     let mut input = ParserInput::new("");
319     assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), None);
320 
321     let mut input = ParserInput::new("width: 320px auto");
322     assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks),
323                Some(ViewportConstraints {
324                    size: initial_viewport,
325 
326                    initial_zoom: PinchZoomFactor::new(1.),
327                    min_zoom: None,
328                    max_zoom: None,
329 
330                    user_zoom: UserZoom::Zoom,
331                    orientation: Orientation::Auto
332                }));
333 
334     let mut input = ParserInput::new("width: 320px auto");
335     assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks),
336                Some(ViewportConstraints {
337                    size: initial_viewport,
338 
339                    initial_zoom: PinchZoomFactor::new(1.),
340                    min_zoom: None,
341                    max_zoom: None,
342 
343                    user_zoom: UserZoom::Zoom,
344                    orientation: Orientation::Auto
345                }));
346 
347     let mut input = ParserInput::new("width: 800px; height: 600px;\
348                                                          zoom: 1;\
349                                                          user-zoom: zoom;\
350                                                          orientation: auto;");
351     assert_eq!(ViewportConstraints::maybe_new(&device,
352                                               from_css!(input),
353                                               QuirksMode::NoQuirks),
354                Some(ViewportConstraints {
355                    size: initial_viewport,
356 
357                    initial_zoom: PinchZoomFactor::new(1.),
358                    min_zoom: None,
359                    max_zoom: None,
360 
361                    user_zoom: UserZoom::Zoom,
362                    orientation: Orientation::Auto
363                }));
364 
365     let initial_viewport = TypedSize2D::new(200., 150.);
366     let device = Device::new(MediaType::screen(), initial_viewport, TypedScale::new(1.0));
367     let mut input = ParserInput::new("width: 320px auto");
368     assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks),
369                Some(ViewportConstraints {
370                    size: TypedSize2D::new(320., 240.),
371 
372                    initial_zoom: PinchZoomFactor::new(1.),
373                    min_zoom: None,
374                    max_zoom: None,
375 
376                    user_zoom: UserZoom::Zoom,
377                    orientation: Orientation::Auto
378                }));
379 }
380