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