Implement CSS Device Adaption § 9 (Viewport <META> element)

Spec: http://dev.w3.org/csswg/css-device-adapt/#viewport-meta
This commit is contained in:
James Gilbertson 2015-05-21 01:08:37 -06:00 committed by Jamie Gilbertson
parent 5d04f8dc8e
commit 61f7a0a1ee
3 changed files with 401 additions and 57 deletions

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::Au; use app_units::Au;
use cssparser::ToCss;
use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important}; use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important};
use euclid::scale_factor::ScaleFactor; use euclid::scale_factor::ScaleFactor;
use euclid::size::{Size2D, TypedSize2D}; use euclid::size::{Size2D, TypedSize2D};
@ -10,20 +11,23 @@ use parser::{ParserContext, log_css_error};
use properties::longhands; use properties::longhands;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::collections::hash_map::{Entry, HashMap}; use std::collections::hash_map::{Entry, HashMap};
use std::fmt;
use std::intrinsics; use std::intrinsics;
use std::iter::Enumerate;
use std::str::Chars;
use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom}; use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
use stylesheets::Origin; use stylesheets::Origin;
use util::geometry::ViewportPx; use util::geometry::ViewportPx;
use values::computed::{Context, ToComputedValue}; use values::computed::{Context, ToComputedValue};
use values::specified::LengthOrPercentageOrAuto; use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength};
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum ViewportDescriptor { pub enum ViewportDescriptor {
MinWidth(LengthOrPercentageOrAuto), MinWidth(ViewportLength),
MaxWidth(LengthOrPercentageOrAuto), MaxWidth(ViewportLength),
MinHeight(LengthOrPercentageOrAuto), MinHeight(ViewportLength),
MaxHeight(LengthOrPercentageOrAuto), MaxHeight(ViewportLength),
Zoom(Zoom), Zoom(Zoom),
MinZoom(Zoom), MinZoom(Zoom),
@ -33,6 +37,98 @@ pub enum ViewportDescriptor {
Orientation(Orientation) Orientation(Orientation)
} }
trait FromMeta: Sized {
fn from_meta<'a>(value: &'a str) -> Option<Self>;
}
// ViewportLength is a length | percentage | auto | extend-to-zoom
// See:
// * http://dev.w3.org/csswg/css-device-adapt/#min-max-width-desc
// * http://dev.w3.org/csswg/css-device-adapt/#extend-to-zoom
#[derive(Copy, Clone, Debug, HeapSizeOf, PartialEq)]
pub enum ViewportLength {
Specified(LengthOrPercentageOrAuto),
ExtendToZoom
}
impl ToCss for ViewportLength {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write
{
match *self {
ViewportLength::Specified(length) => length.to_css(dest),
ViewportLength::ExtendToZoom => write!(dest, "extend-to-zoom"),
}
}
}
impl FromMeta for ViewportLength {
fn from_meta<'a>(value: &'a str) -> Option<ViewportLength> {
macro_rules! specified {
($value:expr) => {
ViewportLength::Specified(LengthOrPercentageOrAuto::Length($value))
}
}
Some(match value {
v if v.eq_ignore_ascii_case("device-width") =>
specified!(Length::ViewportPercentage(ViewportPercentageLength::Vw(100.))),
v if v.eq_ignore_ascii_case("device-height") =>
specified!(Length::ViewportPercentage(ViewportPercentageLength::Vh(100.))),
_ => {
match value.parse::<f32>() {
Ok(n) if n >= 0. => specified!(Length::from_px(n.max(1.).min(10000.))),
Ok(_) => return None,
Err(_) => specified!(Length::from_px(1.))
}
}
})
}
}
impl ViewportLength {
fn parse(input: &mut Parser) -> Result<ViewportLength, ()> {
// we explicitly do not accept 'extend-to-zoom', since it is a UA internal value
// for <META> viewport translation
LengthOrPercentageOrAuto::parse_non_negative(input).map(ViewportLength::Specified)
}
}
impl FromMeta for Zoom {
fn from_meta<'a>(value: &'a str) -> Option<Zoom> {
Some(match value {
v if v.eq_ignore_ascii_case("yes") => Zoom::Number(1.),
v if v.eq_ignore_ascii_case("no") => Zoom::Number(0.1),
v if v.eq_ignore_ascii_case("device-width") => Zoom::Number(10.),
v if v.eq_ignore_ascii_case("device-height") => Zoom::Number(10.),
_ => {
match value.parse::<f32>() {
Ok(n) if n >= 0. => Zoom::Number(n.max(0.1).min(10.)),
Ok(_) => return None,
Err(_) => Zoom::Number(0.1),
}
}
})
}
}
impl FromMeta for UserZoom {
fn from_meta<'a>(value: &'a str) -> Option<UserZoom> {
Some(match value {
v if v.eq_ignore_ascii_case("yes") => UserZoom::Zoom,
v if v.eq_ignore_ascii_case("no") => UserZoom::Fixed,
v if v.eq_ignore_ascii_case("device-width") => UserZoom::Zoom,
v if v.eq_ignore_ascii_case("device-height") => UserZoom::Zoom,
_ => {
match value.parse::<f32>() {
Ok(n) if n >= 1. || n <= -1. => UserZoom::Zoom,
_ => UserZoom::Fixed
}
}
})
}
}
struct ViewportRuleParser<'a, 'b: 'a> { struct ViewportRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b> context: &'a ParserContext<'b>
} }
@ -57,10 +153,10 @@ impl ViewportDescriptorDeclaration {
} }
} }
fn parse_shorthand(input: &mut Parser) -> Result<[LengthOrPercentageOrAuto; 2], ()> { fn parse_shorthand(input: &mut Parser) -> Result<[ViewportLength; 2], ()> {
let min = try!(LengthOrPercentageOrAuto::parse_non_negative(input)); let min = try!(ViewportLength::parse(input));
match input.try(|input| LengthOrPercentageOrAuto::parse_non_negative(input)) { match input.try(|input| ViewportLength::parse(input)) {
Err(()) => Ok([min.clone(), min]), Err(()) => Ok([min, min]),
Ok(max) => Ok([min, max]) Ok(max) => Ok([min, max])
} }
} }
@ -102,16 +198,16 @@ impl<'a, 'b> DeclarationParser for ViewportRuleParser<'a, 'b> {
match name { match name {
n if n.eq_ignore_ascii_case("min-width") => n if n.eq_ignore_ascii_case("min-width") =>
ok!(MinWidth(LengthOrPercentageOrAuto::parse_non_negative)), ok!(MinWidth(ViewportLength::parse)),
n if n.eq_ignore_ascii_case("max-width") => n if n.eq_ignore_ascii_case("max-width") =>
ok!(MaxWidth(LengthOrPercentageOrAuto::parse_non_negative)), ok!(MaxWidth(ViewportLength::parse)),
n if n.eq_ignore_ascii_case("width") => n if n.eq_ignore_ascii_case("width") =>
ok!(shorthand -> [MinWidth, MaxWidth]), ok!(shorthand -> [MinWidth, MaxWidth]),
n if n.eq_ignore_ascii_case("min-height") => n if n.eq_ignore_ascii_case("min-height") =>
ok!(MinHeight(LengthOrPercentageOrAuto::parse_non_negative)), ok!(MinHeight(ViewportLength::parse)),
n if n.eq_ignore_ascii_case("max-height") => n if n.eq_ignore_ascii_case("max-height") =>
ok!(MaxHeight(LengthOrPercentageOrAuto::parse_non_negative)), ok!(MaxHeight(ViewportLength::parse)),
n if n.eq_ignore_ascii_case("height") => n if n.eq_ignore_ascii_case("height") =>
ok!(shorthand -> [MinHeight, MaxHeight]), ok!(shorthand -> [MinHeight, MaxHeight]),
@ -137,6 +233,19 @@ pub struct ViewportRule {
pub declarations: Vec<ViewportDescriptorDeclaration> pub declarations: Vec<ViewportDescriptorDeclaration>
} }
/// Whitespace as defined by DEVICE-ADAPT § 9.2
// TODO: should we just use whitespace as defined by HTML5?
const WHITESPACE: &'static [char] = &['\t', '\n', '\r', ' '];
/// Separators as defined by DEVICE-ADAPT § 9.2
// need to use \x2c instead of ',' due to test-tidy
const SEPARATOR: &'static [char] = &['\x2c', ';'];
#[inline]
fn is_whitespace_separator_or_equals(c: &char) -> bool {
WHITESPACE.contains(c) || SEPARATOR.contains(c) || *c == '='
}
impl ViewportRule { impl ViewportRule {
pub fn parse(input: &mut Parser, context: &ParserContext) pub fn parse(input: &mut Parser, context: &ParserContext)
-> Result<ViewportRule, ()> -> Result<ViewportRule, ()>
@ -166,6 +275,145 @@ impl ViewportRule {
Ok(ViewportRule { declarations: valid_declarations.iter().cascade() }) Ok(ViewportRule { declarations: valid_declarations.iter().cascade() })
} }
pub fn from_meta<'a>(content: &'a str) -> Option<ViewportRule> {
let mut declarations = HashMap::new();
macro_rules! push_descriptor {
($descriptor:ident($value:expr)) => {{
let descriptor = ViewportDescriptor::$descriptor($value);
declarations.insert(
unsafe {
intrinsics::discriminant_value(&descriptor)
},
ViewportDescriptorDeclaration::new(
Origin::Author,
descriptor,
false))
}
}}
let mut has_width = false;
let mut has_height = false;
let mut has_zoom = false;
let mut iter = content.chars().enumerate();
macro_rules! start_of_name {
($iter:ident) => {
$iter.by_ref()
.skip_while(|&(_, c)| is_whitespace_separator_or_equals(&c))
.next()
}
}
while let Some((start, _)) = start_of_name!(iter) {
let property = ViewportRule::parse_meta_property(content,
&mut iter,
start);
if let Some((name, value)) = property {
macro_rules! push {
($descriptor:ident($translate:path)) => {
if let Some(value) = $translate(value) {
push_descriptor!($descriptor(value));
}
}
}
match name {
n if n.eq_ignore_ascii_case("width") => {
if let Some(value) = ViewportLength::from_meta(value) {
push_descriptor!(MinWidth(ViewportLength::ExtendToZoom));
push_descriptor!(MaxWidth(value));
has_width = true;
}
}
n if n.eq_ignore_ascii_case("height") => {
if let Some(value) = ViewportLength::from_meta(value) {
push_descriptor!(MinHeight(ViewportLength::ExtendToZoom));
push_descriptor!(MaxHeight(value));
has_height = true;
}
}
n if n.eq_ignore_ascii_case("initial-scale") => {
if let Some(value) = Zoom::from_meta(value) {
push_descriptor!(Zoom(value));
has_zoom = true;
}
}
n if n.eq_ignore_ascii_case("minimum-scale") =>
push!(MinZoom(Zoom::from_meta)),
n if n.eq_ignore_ascii_case("maximum-scale") =>
push!(MaxZoom(Zoom::from_meta)),
n if n.eq_ignore_ascii_case("user-scalable") =>
push!(UserZoom(UserZoom::from_meta)),
_ => {}
}
}
}
// DEVICE-ADAPT § 9.4 - The 'width' and 'height' properties
// http://dev.w3.org/csswg/css-device-adapt/#width-and-height-properties
if !has_width && has_zoom {
if has_height {
push_descriptor!(MinWidth(ViewportLength::Specified(LengthOrPercentageOrAuto::Auto)));
push_descriptor!(MaxWidth(ViewportLength::Specified(LengthOrPercentageOrAuto::Auto)));
} else {
push_descriptor!(MinWidth(ViewportLength::ExtendToZoom));
push_descriptor!(MaxWidth(ViewportLength::ExtendToZoom));
}
}
let declarations: Vec<_> = declarations.into_iter().map(|kv| kv.1).collect();
if !declarations.is_empty() {
Some(ViewportRule { declarations: declarations })
} else {
None
}
}
fn parse_meta_property<'a>(content: &'a str,
iter: &mut Enumerate<Chars<'a>>,
start: usize)
-> Option<(&'a str, &'a str)>
{
fn end_of_token<'a>(iter: &mut Enumerate<Chars<'a>>) -> Option<(usize, char)> {
iter.by_ref()
.skip_while(|&(_, c)| !is_whitespace_separator_or_equals(&c))
.next()
}
fn skip_whitespace<'a>(iter: &mut Enumerate<Chars<'a>>) -> Option<(usize, char)> {
iter.by_ref()
.skip_while(|&(_, c)| WHITESPACE.contains(&c))
.next()
}
// <name> <whitespace>* '='
let end = match end_of_token(iter) {
Some((end, c)) if WHITESPACE.contains(&c) => {
match skip_whitespace(iter) {
Some((_, c)) if c == '=' => end,
_ => return None
}
}
Some((end, c)) if c == '=' => end,
_ => return None
};
let name = &content[start..end];
// <whitespace>* <value>
let start = match skip_whitespace(iter) {
Some((start, c)) if !SEPARATOR.contains(&c) => start,
_ => return None
};
let value = match end_of_token(iter) {
Some((end, _)) => &content[start..end],
_ => &content[start..]
};
Some((name, value))
}
} }
pub trait ViewportRuleCascade: Iterator + Sized { pub trait ViewportRuleCascade: Iterator + Sized {
@ -306,9 +554,9 @@ impl MaybeNew for ViewportConstraints {
($op:ident, $opta:expr, $optb:expr) => { ($op:ident, $opta:expr, $optb:expr) => {
match ($opta, $optb) { match ($opta, $optb) {
(None, None) => None, (None, None) => None,
(a, None) => a.clone(), (a, None) => a,
(None, b) => b.clone(), (None, b) => b,
(a, b) => Some(a.clone().unwrap().$op(b.clone().unwrap())), (a, b) => Some(a.unwrap().$op(b.unwrap())),
} }
} }
} }
@ -325,7 +573,7 @@ impl MaybeNew for ViewportConstraints {
// DEVICE-ADAPT § 6.2.1 Resolve min-zoom and max-zoom values // DEVICE-ADAPT § 6.2.1 Resolve min-zoom and max-zoom values
if min_zoom.is_some() && max_zoom.is_some() { if min_zoom.is_some() && max_zoom.is_some() {
max_zoom = Some(min_zoom.clone().unwrap().max(max_zoom.unwrap())) max_zoom = Some(min_zoom.unwrap().max(max_zoom.unwrap()))
} }
// DEVICE-ADAPT § 6.2.2 Constrain zoom value to the [min-zoom, max-zoom] range // DEVICE-ADAPT § 6.2.2 Constrain zoom value to the [min-zoom, max-zoom] range
@ -363,18 +611,41 @@ impl MaybeNew for ViewportConstraints {
outline_style_present: false, outline_style_present: false,
}; };
// DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom'
let extend_width;
let extend_height;
if let Some(extend_zoom) = max!(initial_zoom, max_zoom) {
let scale_factor = 1. / extend_zoom;
extend_width = Some(initial_viewport.width.scale_by(scale_factor));
extend_height = Some(initial_viewport.height.scale_by(scale_factor));
} else {
extend_width = None;
extend_height = None;
}
macro_rules! to_pixel_length { macro_rules! to_pixel_length {
($value:ident, $dimension:ident) => { ($value:ident, $dimension:ident, $extend_to:ident => $auto_extend_to:expr) => {
if let Some($value) = $value { if let Some($value) = $value {
match $value { match $value {
LengthOrPercentageOrAuto::Length(value) => ViewportLength::Specified(length) => match length {
Some(value.to_computed_value(&context)), LengthOrPercentageOrAuto::Length(value) =>
LengthOrPercentageOrAuto::Percentage(value) => Some(value.to_computed_value(&context)),
Some(initial_viewport.$dimension.scale_by(value.0)), LengthOrPercentageOrAuto::Percentage(value) =>
LengthOrPercentageOrAuto::Auto => None, Some(initial_viewport.$dimension.scale_by(value.0)),
LengthOrPercentageOrAuto::Calc(calc) => { LengthOrPercentageOrAuto::Auto => None,
let calc = calc.to_computed_value(&context); LengthOrPercentageOrAuto::Calc(calc) => {
Some(initial_viewport.$dimension.scale_by(calc.percentage()) + calc.length()) let calc = calc.to_computed_value(&context);
Some(initial_viewport.$dimension.scale_by(calc.percentage()) + calc.length())
}
},
ViewportLength::ExtendToZoom => {
// $extend_to will be 'None' if 'extend-to-zoom' is 'auto'
match ($extend_to, $auto_extend_to) {
(None, None) => None,
(a, None) => a,
(None, b) => b,
(a, b) => cmp::max(a, b)
}
} }
} }
} else { } else {
@ -383,10 +654,14 @@ impl MaybeNew for ViewportConstraints {
} }
} }
let min_width = to_pixel_length!(min_width, width); // DEVICE-ADAPT § 9.3 states that max-descriptors need to be resolved
let max_width = to_pixel_length!(max_width, width); // before min-descriptors.
let min_height = to_pixel_length!(min_height, height); // http://dev.w3.org/csswg/css-device-adapt/#resolve-extend-to-zoom
let max_height = to_pixel_length!(max_height, height); let max_width = to_pixel_length!(max_width, width, extend_width => None);
let max_height = to_pixel_length!(max_height, height, extend_height => None);
let min_width = to_pixel_length!(min_width, width, extend_width => max_width);
let min_height = to_pixel_length!(min_height, height, extend_height => max_height);
// DEVICE-ADAPT § 6.2.4 Resolve initial width and height from min/max descriptors // DEVICE-ADAPT § 6.2.4 Resolve initial width and height from min/max descriptors
macro_rules! resolve { macro_rules! resolve {
@ -421,7 +696,7 @@ impl MaybeNew for ViewportConstraints {
Au(0) => initial_viewport.width, Au(0) => initial_viewport.width,
initial_height => { initial_height => {
let ratio = initial_viewport.width.to_f32_px() / initial_height.to_f32_px(); let ratio = initial_viewport.width.to_f32_px() / initial_height.to_f32_px();
Au::from_f32_px(height.clone().unwrap().to_f32_px() * ratio) Au::from_f32_px(height.unwrap().to_f32_px() * ratio)
} }
}); });

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![feature(plugin)] #![feature(plugin)]
#![cfg_attr(test, feature(core_intrinsics))]
#![plugin(string_cache_plugin)] #![plugin(string_cache_plugin)]
extern crate app_units; extern crate app_units;

View file

@ -8,7 +8,9 @@ use euclid::size::Size2D;
use style::media_queries::{Device, MediaType}; use style::media_queries::{Device, MediaType};
use style::parser::ParserContext; use style::parser::ParserContext;
use style::stylesheets::{Origin, Stylesheet, CSSRuleIteratorExt}; use style::stylesheets::{Origin, Stylesheet, CSSRuleIteratorExt};
use style::values::specified::{Length, LengthOrPercentageOrAuto}; use style::values::specified::Length::{self, ViewportPercentage};
use style::values::specified::LengthOrPercentageOrAuto::{self, Auto};
use style::values::specified::ViewportPercentageLength::Vw;
use style::viewport::*; use style::viewport::*;
use style_traits::viewport::*; use style_traits::viewport::*;
use url::Url; use url::Url;
@ -38,6 +40,26 @@ fn test_viewport_rule<F>(css: &str,
assert!(rule_count > 0); assert!(rule_count > 0);
} }
fn test_meta_viewport<F>(meta: &str, callback: F)
where F: Fn(&Vec<ViewportDescriptorDeclaration>, &str)
{
if let Some(mut rule) = ViewportRule::from_meta(meta) {
use std::intrinsics::discriminant_value;
// 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 = unsafe { discriminant_value(&a.descriptor) };
let b = unsafe { discriminant_value(&b.descriptor) };
a.cmp(&b)
});
callback(&rule.declarations, meta);
} else {
panic!("no @viewport rule for {}", meta);
}
}
macro_rules! assert_decl_len { macro_rules! assert_decl_len {
($declarations:ident == 1) => { ($declarations:ident == 1) => {
assert!($declarations.len() == 1, assert!($declarations.len() == 1,
@ -51,6 +73,15 @@ macro_rules! assert_decl_len {
} }
} }
macro_rules! viewport_length {
($value:expr, px) => {
ViewportLength::Specified(LengthOrPercentageOrAuto::Length(Length::from_px($value)))
};
($value:expr, vw) => {
ViewportLength::Specified(LengthOrPercentageOrAuto::Length(ViewportPercentage(Vw($value))))
}
}
#[test] #[test]
fn empty_viewport_rule() { fn empty_viewport_rule() {
let device = Device::new(MediaType::Screen, Size2D::typed(800., 600.)); let device = Device::new(MediaType::Screen, Size2D::typed(800., 600.));
@ -84,10 +115,10 @@ fn simple_viewport_rules() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 9); assert_decl_len!(declarations == 9);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
assert_decl_eq!(&declarations[2], Author, MinHeight: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[2], Author, MinHeight: ViewportLength::Specified(Auto));
assert_decl_eq!(&declarations[3], Author, MaxHeight: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[3], Author, MaxHeight: ViewportLength::Specified(Auto));
assert_decl_eq!(&declarations[4], Author, Zoom: Zoom::Auto); assert_decl_eq!(&declarations[4], Author, Zoom: Zoom::Auto);
assert_decl_eq!(&declarations[5], Author, MinZoom: Zoom::Number(0.)); assert_decl_eq!(&declarations[5], Author, MinZoom: Zoom::Number(0.));
assert_decl_eq!(&declarations[6], Author, MaxZoom: Zoom::Percentage(2.)); assert_decl_eq!(&declarations[6], Author, MaxZoom: Zoom::Percentage(2.));
@ -100,10 +131,49 @@ fn simple_viewport_rules() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 4); assert_decl_len!(declarations == 4);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(200.))); assert_decl_eq!(&declarations[0], Author, MinWidth: viewport_length!(200., px));
assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
assert_decl_eq!(&declarations[2], Author, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.))); assert_decl_eq!(&declarations[2], Author, MinHeight: viewport_length!(200., px));
assert_decl_eq!(&declarations[3], Author, MaxHeight: LengthOrPercentageOrAuto::Auto); 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);
}); });
} }
@ -116,7 +186,7 @@ fn cascading_within_viewport_rule() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 1); assert_decl_len!(declarations == 1);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
}); });
// !important order of appearance // !important order of appearance
@ -124,7 +194,7 @@ fn cascading_within_viewport_rule() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 1); assert_decl_len!(declarations == 1);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
}); });
// !important vs normal // !important vs normal
@ -132,7 +202,7 @@ fn cascading_within_viewport_rule() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 1); assert_decl_len!(declarations == 1);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
}); });
// normal longhands vs normal shorthand // normal longhands vs normal shorthand
@ -140,8 +210,8 @@ fn cascading_within_viewport_rule() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 2); assert_decl_len!(declarations == 2);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
}); });
// normal shorthand vs normal longhands // normal shorthand vs normal longhands
@ -149,8 +219,8 @@ fn cascading_within_viewport_rule() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 2); assert_decl_len!(declarations == 2);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto));
assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto));
}); });
// one !important longhand vs normal shorthand // one !important longhand vs normal shorthand
@ -158,8 +228,8 @@ fn cascading_within_viewport_rule() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 2); assert_decl_len!(declarations == 2);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Length(Length::from_px(200.))); assert_decl_eq!(&declarations[1], Author, MaxWidth: viewport_length!(200., px));
}); });
// both !important longhands vs normal shorthand // both !important longhands vs normal shorthand
@ -167,8 +237,8 @@ fn cascading_within_viewport_rule() {
&device, |declarations, css| { &device, |declarations, css| {
println!("{}", css); println!("{}", css);
assert_decl_len!(declarations == 2); assert_decl_len!(declarations == 2);
assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important); assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important);
assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto, !important); assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto), !important);
}); });
} }
@ -189,8 +259,8 @@ fn multiple_stylesheets_cascading() {
.declarations; .declarations;
assert_decl_len!(declarations == 3); assert_decl_len!(declarations == 3);
assert_decl_eq!(&declarations[0], UserAgent, Zoom: Zoom::Number(1.)); assert_decl_eq!(&declarations[0], UserAgent, Zoom: Zoom::Number(1.));
assert_decl_eq!(&declarations[1], User, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.))); assert_decl_eq!(&declarations[1], User, MinHeight: viewport_length!(200., px));
assert_decl_eq!(&declarations[2], Author, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(300.))); assert_decl_eq!(&declarations[2], Author, MinWidth: viewport_length!(300., px));
let stylesheets = vec![ let stylesheets = vec![
stylesheet!("@viewport { min-width: 100px !important; }", UserAgent), stylesheet!("@viewport { min-width: 100px !important; }", UserAgent),
@ -203,10 +273,8 @@ fn multiple_stylesheets_cascading() {
.cascade() .cascade()
.declarations; .declarations;
assert_decl_len!(declarations == 3); assert_decl_len!(declarations == 3);
assert_decl_eq!( assert_decl_eq!(&declarations[0], UserAgent, MinWidth: viewport_length!(100., px), !important);
&declarations[0], UserAgent, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(100.)), !important); assert_decl_eq!(&declarations[1], User, MinHeight: viewport_length!(200., px), !important);
assert_decl_eq!(
&declarations[1], User, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.)), !important);
assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(3.), !important); assert_decl_eq!(&declarations[2], Author, Zoom: Zoom::Number(3.), !important);
} }