media_queries.rs (19629B)
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 https://mozilla.org/MPL/2.0/. */ 4 5 use euclid::Scale; 6 use euclid::Size2D; 7 use servo_arc::Arc; 8 use servo_url::ServoUrl; 9 use std::borrow::ToOwned; 10 use style::context::QuirksMode; 11 use style::media_queries::*; 12 use style::servo::media_queries::*; 13 use style::shared_lock::SharedRwLock; 14 use style::stylesheets::{AllRules, CssRule, Origin, Stylesheet, StylesheetInDocument}; 15 use style::values::{specified, CustomIdent}; 16 use style::Atom; 17 use style_traits::ToCss; 18 19 fn test_media_rule<F>(css: &str, callback: F) 20 where 21 F: Fn(&MediaList, &str), 22 { 23 let url = ServoUrl::parse("http://localhost").unwrap(); 24 let css_str = css.to_owned(); 25 let lock = SharedRwLock::new(); 26 let media_list = Arc::new(lock.wrap(MediaList::empty())); 27 let stylesheet = Stylesheet::from_str( 28 css, 29 url, 30 Origin::Author, 31 media_list, 32 lock, 33 None, 34 None, 35 QuirksMode::NoQuirks, 36 0, 37 ); 38 let dummy = Device::new( 39 MediaType::screen(), 40 Size2D::new(200.0, 100.0), 41 Scale::new(1.0), 42 ); 43 let mut rule_count = 0; 44 let guard = stylesheet.shared_lock.read(); 45 for rule in stylesheet.iter_rules::<AllRules>(&dummy, &guard) { 46 if let CssRule::Media(ref lock) = *rule { 47 rule_count += 1; 48 callback(&lock.read_with(&guard).media_queries.read_with(&guard), css); 49 } 50 } 51 assert!(rule_count > 0, css_str); 52 } 53 54 fn media_query_test(device: &Device, css: &str, expected_rule_count: usize) { 55 let url = ServoUrl::parse("http://localhost").unwrap(); 56 let lock = SharedRwLock::new(); 57 let media_list = Arc::new(lock.wrap(MediaList::empty())); 58 let ss = Stylesheet::from_str( 59 css, 60 url, 61 Origin::Author, 62 media_list, 63 lock, 64 None, 65 None, 66 QuirksMode::NoQuirks, 67 0, 68 ); 69 let mut rule_count = 0; 70 ss.effective_style_rules(device, &ss.shared_lock.read(), |_| rule_count += 1); 71 assert!(rule_count == expected_rule_count, css.to_owned()); 72 } 73 74 #[test] 75 fn test_mq_empty() { 76 test_media_rule("@media { }", |list, css| { 77 assert!(list.media_queries.len() == 0, css.to_owned()); 78 }); 79 } 80 81 #[test] 82 fn test_mq_screen() { 83 test_media_rule("@media screen { }", |list, css| { 84 assert!(list.media_queries.len() == 1, css.to_owned()); 85 let q = &list.media_queries[0]; 86 assert!(q.qualifier == None, css.to_owned()); 87 assert!( 88 q.media_type == MediaQueryType::Concrete(MediaType::screen()), 89 css.to_owned() 90 ); 91 assert!(q.expressions.len() == 0, css.to_owned()); 92 }); 93 94 test_media_rule("@media only screen { }", |list, css| { 95 assert!(list.media_queries.len() == 1, css.to_owned()); 96 let q = &list.media_queries[0]; 97 assert!(q.qualifier == Some(Qualifier::Only), css.to_owned()); 98 assert!( 99 q.media_type == MediaQueryType::Concrete(MediaType::screen()), 100 css.to_owned() 101 ); 102 assert!(q.expressions.len() == 0, css.to_owned()); 103 }); 104 105 test_media_rule("@media not screen { }", |list, css| { 106 assert!(list.media_queries.len() == 1, css.to_owned()); 107 let q = &list.media_queries[0]; 108 assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); 109 assert!( 110 q.media_type == MediaQueryType::Concrete(MediaType::screen()), 111 css.to_owned() 112 ); 113 assert!(q.expressions.len() == 0, css.to_owned()); 114 }); 115 } 116 117 #[test] 118 fn test_mq_print() { 119 test_media_rule("@media print { }", |list, css| { 120 assert!(list.media_queries.len() == 1, css.to_owned()); 121 let q = &list.media_queries[0]; 122 assert!(q.qualifier == None, css.to_owned()); 123 assert!( 124 q.media_type == MediaQueryType::Concrete(MediaType::print()), 125 css.to_owned() 126 ); 127 assert!(q.expressions.len() == 0, css.to_owned()); 128 }); 129 130 test_media_rule("@media only print { }", |list, css| { 131 assert!(list.media_queries.len() == 1, css.to_owned()); 132 let q = &list.media_queries[0]; 133 assert!(q.qualifier == Some(Qualifier::Only), css.to_owned()); 134 assert!( 135 q.media_type == MediaQueryType::Concrete(MediaType::print()), 136 css.to_owned() 137 ); 138 assert!(q.expressions.len() == 0, css.to_owned()); 139 }); 140 141 test_media_rule("@media not print { }", |list, css| { 142 assert!(list.media_queries.len() == 1, css.to_owned()); 143 let q = &list.media_queries[0]; 144 assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); 145 assert!( 146 q.media_type == MediaQueryType::Concrete(MediaType::print()), 147 css.to_owned() 148 ); 149 assert!(q.expressions.len() == 0, css.to_owned()); 150 }); 151 } 152 153 #[test] 154 fn test_mq_unknown() { 155 test_media_rule("@media fridge { }", |list, css| { 156 assert!(list.media_queries.len() == 1, css.to_owned()); 157 let q = &list.media_queries[0]; 158 assert!(q.qualifier == None, css.to_owned()); 159 assert!( 160 q.media_type == MediaQueryType::Concrete(MediaType(CustomIdent(Atom::from("fridge")))), 161 css.to_owned() 162 ); 163 assert!(q.expressions.len() == 0, css.to_owned()); 164 }); 165 166 test_media_rule("@media only glass { }", |list, css| { 167 assert!(list.media_queries.len() == 1, css.to_owned()); 168 let q = &list.media_queries[0]; 169 assert!(q.qualifier == Some(Qualifier::Only), css.to_owned()); 170 assert!( 171 q.media_type == MediaQueryType::Concrete(MediaType(CustomIdent(Atom::from("glass")))), 172 css.to_owned() 173 ); 174 assert!(q.expressions.len() == 0, css.to_owned()); 175 }); 176 177 test_media_rule("@media not wood { }", |list, css| { 178 assert!(list.media_queries.len() == 1, css.to_owned()); 179 let q = &list.media_queries[0]; 180 assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); 181 assert!( 182 q.media_type == MediaQueryType::Concrete(MediaType(CustomIdent(Atom::from("wood")))), 183 css.to_owned() 184 ); 185 assert!(q.expressions.len() == 0, css.to_owned()); 186 }); 187 } 188 189 #[test] 190 fn test_mq_all() { 191 test_media_rule("@media all { }", |list, css| { 192 assert!(list.media_queries.len() == 1, css.to_owned()); 193 let q = &list.media_queries[0]; 194 assert!(q.qualifier == None, css.to_owned()); 195 assert!(q.media_type == MediaQueryType::All, css.to_owned()); 196 assert!(q.expressions.len() == 0, css.to_owned()); 197 }); 198 199 test_media_rule("@media only all { }", |list, css| { 200 assert!(list.media_queries.len() == 1, css.to_owned()); 201 let q = &list.media_queries[0]; 202 assert!(q.qualifier == Some(Qualifier::Only), css.to_owned()); 203 assert!(q.media_type == MediaQueryType::All, css.to_owned()); 204 assert!(q.expressions.len() == 0, css.to_owned()); 205 }); 206 207 test_media_rule("@media not all { }", |list, css| { 208 assert!(list.media_queries.len() == 1, css.to_owned()); 209 let q = &list.media_queries[0]; 210 assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); 211 assert!(q.media_type == MediaQueryType::All, css.to_owned()); 212 assert!(q.expressions.len() == 0, css.to_owned()); 213 }); 214 } 215 216 #[test] 217 fn test_mq_or() { 218 test_media_rule("@media screen, print { }", |list, css| { 219 assert!(list.media_queries.len() == 2, css.to_owned()); 220 let q0 = &list.media_queries[0]; 221 assert!(q0.qualifier == None, css.to_owned()); 222 assert!( 223 q0.media_type == MediaQueryType::Concrete(MediaType::screen()), 224 css.to_owned() 225 ); 226 assert!(q0.expressions.len() == 0, css.to_owned()); 227 228 let q1 = &list.media_queries[1]; 229 assert!(q1.qualifier == None, css.to_owned()); 230 assert!( 231 q1.media_type == MediaQueryType::Concrete(MediaType::print()), 232 css.to_owned() 233 ); 234 assert!(q1.expressions.len() == 0, css.to_owned()); 235 }); 236 } 237 238 #[test] 239 fn test_mq_default_expressions() { 240 test_media_rule("@media (min-width: 100px) { }", |list, css| { 241 assert!(list.media_queries.len() == 1, css.to_owned()); 242 let q = &list.media_queries[0]; 243 assert!(q.qualifier == None, css.to_owned()); 244 assert!(q.media_type == MediaQueryType::All, css.to_owned()); 245 assert!(q.expressions.len() == 1, css.to_owned()); 246 match *q.expressions[0].kind_for_testing() { 247 ExpressionKind::Width(Range::Min(ref w)) => { 248 assert!(*w == specified::Length::from_px(100.)) 249 }, 250 _ => panic!("wrong expression type"), 251 } 252 }); 253 254 test_media_rule("@media (max-width: 43px) { }", |list, css| { 255 assert!(list.media_queries.len() == 1, css.to_owned()); 256 let q = &list.media_queries[0]; 257 assert!(q.qualifier == None, css.to_owned()); 258 assert!(q.media_type == MediaQueryType::All, css.to_owned()); 259 assert!(q.expressions.len() == 1, css.to_owned()); 260 match *q.expressions[0].kind_for_testing() { 261 ExpressionKind::Width(Range::Max(ref w)) => { 262 assert!(*w == specified::Length::from_px(43.)) 263 }, 264 _ => panic!("wrong expression type"), 265 } 266 }); 267 } 268 269 #[test] 270 fn test_mq_expressions() { 271 test_media_rule("@media screen and (min-width: 100px) { }", |list, css| { 272 assert!(list.media_queries.len() == 1, css.to_owned()); 273 let q = &list.media_queries[0]; 274 assert!(q.qualifier == None, css.to_owned()); 275 assert!( 276 q.media_type == MediaQueryType::Concrete(MediaType::screen()), 277 css.to_owned() 278 ); 279 assert!(q.expressions.len() == 1, css.to_owned()); 280 match *q.expressions[0].kind_for_testing() { 281 ExpressionKind::Width(Range::Min(ref w)) => { 282 assert!(*w == specified::Length::from_px(100.)) 283 }, 284 _ => panic!("wrong expression type"), 285 } 286 }); 287 288 test_media_rule("@media print and (max-width: 43px) { }", |list, css| { 289 assert!(list.media_queries.len() == 1, css.to_owned()); 290 let q = &list.media_queries[0]; 291 assert!(q.qualifier == None, css.to_owned()); 292 assert!( 293 q.media_type == MediaQueryType::Concrete(MediaType::print()), 294 css.to_owned() 295 ); 296 assert!(q.expressions.len() == 1, css.to_owned()); 297 match *q.expressions[0].kind_for_testing() { 298 ExpressionKind::Width(Range::Max(ref w)) => { 299 assert!(*w == specified::Length::from_px(43.)) 300 }, 301 _ => panic!("wrong expression type"), 302 } 303 }); 304 305 test_media_rule("@media print and (width: 43px) { }", |list, css| { 306 assert!(list.media_queries.len() == 1, css.to_owned()); 307 let q = &list.media_queries[0]; 308 assert!(q.qualifier == None, css.to_owned()); 309 assert!( 310 q.media_type == MediaQueryType::Concrete(MediaType::print()), 311 css.to_owned() 312 ); 313 assert!(q.expressions.len() == 1, css.to_owned()); 314 match *q.expressions[0].kind_for_testing() { 315 ExpressionKind::Width(Range::Eq(ref w)) => { 316 assert!(*w == specified::Length::from_px(43.)) 317 }, 318 _ => panic!("wrong expression type"), 319 } 320 }); 321 322 test_media_rule("@media fridge and (max-width: 52px) { }", |list, css| { 323 assert!(list.media_queries.len() == 1, css.to_owned()); 324 let q = &list.media_queries[0]; 325 assert!(q.qualifier == None, css.to_owned()); 326 assert!( 327 q.media_type == MediaQueryType::Concrete(MediaType(CustomIdent(Atom::from("fridge")))), 328 css.to_owned() 329 ); 330 assert!(q.expressions.len() == 1, css.to_owned()); 331 match *q.expressions[0].kind_for_testing() { 332 ExpressionKind::Width(Range::Max(ref w)) => { 333 assert!(*w == specified::Length::from_px(52.)) 334 }, 335 _ => panic!("wrong expression type"), 336 } 337 }); 338 } 339 340 #[test] 341 fn test_to_css() { 342 test_media_rule("@media print and (width: 43px) { }", |list, _| { 343 let q = &list.media_queries[0]; 344 let dest = q.to_css_string(); 345 assert_eq!(dest, "print and (width: 43px)"); 346 }); 347 } 348 349 #[test] 350 fn test_mq_multiple_expressions() { 351 test_media_rule( 352 "@media (min-width: 100px) and (max-width: 200px) { }", 353 |list, css| { 354 assert!(list.media_queries.len() == 1, css.to_owned()); 355 let q = &list.media_queries[0]; 356 assert!(q.qualifier == None, css.to_owned()); 357 assert!(q.media_type == MediaQueryType::All, css.to_owned()); 358 assert!(q.expressions.len() == 2, css.to_owned()); 359 match *q.expressions[0].kind_for_testing() { 360 ExpressionKind::Width(Range::Min(ref w)) => { 361 assert!(*w == specified::Length::from_px(100.)) 362 }, 363 _ => panic!("wrong expression type"), 364 } 365 match *q.expressions[1].kind_for_testing() { 366 ExpressionKind::Width(Range::Max(ref w)) => { 367 assert!(*w == specified::Length::from_px(200.)) 368 }, 369 _ => panic!("wrong expression type"), 370 } 371 }, 372 ); 373 374 test_media_rule( 375 "@media not screen and (min-width: 100px) and (max-width: 200px) { }", 376 |list, css| { 377 assert!(list.media_queries.len() == 1, css.to_owned()); 378 let q = &list.media_queries[0]; 379 assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); 380 assert!( 381 q.media_type == MediaQueryType::Concrete(MediaType::screen()), 382 css.to_owned() 383 ); 384 assert!(q.expressions.len() == 2, css.to_owned()); 385 match *q.expressions[0].kind_for_testing() { 386 ExpressionKind::Width(Range::Min(ref w)) => { 387 assert!(*w == specified::Length::from_px(100.)) 388 }, 389 _ => panic!("wrong expression type"), 390 } 391 match *q.expressions[1].kind_for_testing() { 392 ExpressionKind::Width(Range::Max(ref w)) => { 393 assert!(*w == specified::Length::from_px(200.)) 394 }, 395 _ => panic!("wrong expression type"), 396 } 397 }, 398 ); 399 } 400 401 #[test] 402 fn test_mq_malformed_expressions() { 403 fn check_malformed_expr(list: &MediaList, css: &str) { 404 assert!(!list.media_queries.is_empty(), css.to_owned()); 405 for mq in &list.media_queries { 406 assert!(mq.qualifier == Some(Qualifier::Not), css.to_owned()); 407 assert!(mq.media_type == MediaQueryType::All, css.to_owned()); 408 assert!(mq.expressions.is_empty(), css.to_owned()); 409 } 410 } 411 412 for rule in &[ 413 "@media (min-width: 100blah) and (max-width: 200px) { }", 414 "@media screen and (height: 200px) { }", 415 "@media (min-width: 30em foo bar) {}", 416 "@media not {}", 417 "@media not (min-width: 300px) {}", 418 "@media , {}", 419 ] { 420 test_media_rule(rule, check_malformed_expr); 421 } 422 } 423 424 #[test] 425 fn test_matching_simple() { 426 let device = Device::new( 427 MediaType::screen(), 428 Size2D::new(200.0, 100.0), 429 Scale::new(1.0), 430 ); 431 432 media_query_test(&device, "@media not all { a { color: red; } }", 0); 433 media_query_test(&device, "@media not screen { a { color: red; } }", 0); 434 media_query_test(&device, "@media not print { a { color: red; } }", 1); 435 436 media_query_test(&device, "@media unknown { a { color: red; } }", 0); 437 media_query_test(&device, "@media not unknown { a { color: red; } }", 1); 438 439 media_query_test(&device, "@media { a { color: red; } }", 1); 440 media_query_test(&device, "@media screen { a { color: red; } }", 1); 441 media_query_test(&device, "@media print { a { color: red; } }", 0); 442 } 443 444 #[test] 445 fn test_matching_width() { 446 let device = Device::new( 447 MediaType::screen(), 448 Size2D::new(200.0, 100.0), 449 Scale::new(1.0), 450 ); 451 452 media_query_test(&device, "@media { a { color: red; } }", 1); 453 454 media_query_test(&device, "@media (min-width: 50px) { a { color: red; } }", 1); 455 media_query_test( 456 &device, 457 "@media (min-width: 150px) { a { color: red; } }", 458 1, 459 ); 460 media_query_test( 461 &device, 462 "@media (min-width: 300px) { a { color: red; } }", 463 0, 464 ); 465 466 media_query_test( 467 &device, 468 "@media screen and (min-width: 50px) { a { color: red; } }", 469 1, 470 ); 471 media_query_test( 472 &device, 473 "@media screen and (min-width: 150px) { a { color: red; } }", 474 1, 475 ); 476 media_query_test( 477 &device, 478 "@media screen and (min-width: 300px) { a { color: red; } }", 479 0, 480 ); 481 482 media_query_test( 483 &device, 484 "@media not screen and (min-width: 50px) { a { color: red; } }", 485 0, 486 ); 487 media_query_test( 488 &device, 489 "@media not screen and (min-width: 150px) { a { color: red; } }", 490 0, 491 ); 492 media_query_test( 493 &device, 494 "@media not screen and (min-width: 300px) { a { color: red; } }", 495 1, 496 ); 497 498 media_query_test(&device, "@media (max-width: 50px) { a { color: red; } }", 0); 499 media_query_test( 500 &device, 501 "@media (max-width: 150px) { a { color: red; } }", 502 0, 503 ); 504 media_query_test( 505 &device, 506 "@media (max-width: 300px) { a { color: red; } }", 507 1, 508 ); 509 510 media_query_test( 511 &device, 512 "@media screen and (min-width: 50px) and (max-width: 100px) { a { color: red; } }", 513 0, 514 ); 515 media_query_test( 516 &device, 517 "@media screen and (min-width: 250px) and (max-width: 300px) { a { color: red; } }", 518 0, 519 ); 520 media_query_test( 521 &device, 522 "@media screen and (min-width: 50px) and (max-width: 250px) { a { color: red; } }", 523 1, 524 ); 525 526 media_query_test( 527 &device, 528 "@media not screen and (min-width: 50px) and (max-width: 100px) { a { color: red; } }", 529 1, 530 ); 531 media_query_test( 532 &device, 533 "@media not screen and (min-width: 250px) and (max-width: 300px) { a { color: red; } }", 534 1, 535 ); 536 media_query_test( 537 &device, 538 "@media not screen and (min-width: 50px) and (max-width: 250px) { a { color: red; } }", 539 0, 540 ); 541 542 media_query_test( 543 &device, 544 "@media not screen and (min-width: 3.1em) and (max-width: 6em) { a { color: red; } }", 545 1, 546 ); 547 media_query_test( 548 &device, 549 "@media not screen and (min-width: 16em) and (max-width: 19.75em) { a { color: red; } }", 550 1, 551 ); 552 media_query_test( 553 &device, 554 "@media not screen and (min-width: 3em) and (max-width: 250px) { a { color: red; } }", 555 0, 556 ); 557 } 558 559 #[test] 560 fn test_matching_invalid() { 561 let device = Device::new( 562 MediaType::screen(), 563 Size2D::new(200.0, 100.0), 564 Scale::new(1.0), 565 ); 566 567 media_query_test(&device, "@media fridge { a { color: red; } }", 0); 568 media_query_test( 569 &device, 570 "@media screen and (height: 100px) { a { color: red; } }", 571 0, 572 ); 573 media_query_test( 574 &device, 575 "@media not print and (width: 100) { a { color: red; } }", 576 0, 577 ); 578 }