diff options
author | bors-servo <metajack+bors@gmail.com> | 2015-09-30 22:49:41 -0600 |
---|---|---|
committer | bors-servo <metajack+bors@gmail.com> | 2015-09-30 22:49:41 -0600 |
commit | a7743052cad0387733c90940fa91733ede32772c (patch) | |
tree | a84bb75c3ac7df84f34b19adb51438f39fc0b816 | |
parent | 0860be4c28ebaee36a0df4de3510d917b0e08cb7 (diff) | |
parent | d0ace58452c704a3888b3a32a8242a5217b44274 (diff) | |
download | servo-a7743052cad0387733c90940fa91733ede32772c.tar.gz servo-a7743052cad0387733c90940fa91733ede32772c.zip |
Auto merge of #6185 - luniv:viewport-meta, r=mbrubeck
Implement <meta name=viewport> handling
Translate <meta name=viewport> as according to [CSS Device Adaption § 9](http://dev.w3.org/csswg/css-device-adapt/#viewport-meta)
Note: as the PR currently stands, handling `<meta name=viewport>` elements always occurs. This is probably not desired for some contexts (e.g. desktop), but I'm unsure of how to conditionally handle elements based on that.
<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/6185)
<!-- Reviewable:end -->
-rw-r--r-- | components/layout/layout_task.rs | 20 | ||||
-rw-r--r-- | components/script/dom/htmlmetaelement.rs | 57 | ||||
-rw-r--r-- | components/script/dom/virtualmethods.rs | 5 | ||||
-rw-r--r-- | components/script/layout_interface.rs | 4 | ||||
-rw-r--r-- | components/style/viewport.rs | 337 | ||||
-rw-r--r-- | tests/ref/basic.list | 1 | ||||
-rw-r--r-- | tests/ref/viewport_meta.html | 25 | ||||
-rw-r--r-- | tests/unit/style/lib.rs | 1 | ||||
-rw-r--r-- | tests/unit/style/viewport.rs | 120 |
9 files changed, 508 insertions, 62 deletions
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 3535b8137df..ee77270e62b 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -78,8 +78,9 @@ use style::media_queries::{Device, MediaQueryList, MediaType}; use style::properties::longhands::{display, position}; use style::properties::style_structs; use style::selector_matching::Stylist; -use style::stylesheets::{CSSRuleIteratorExt, Origin, Stylesheet}; +use style::stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet}; use style::values::AuExtensionMethods; +use style::viewport::ViewportRule; use url::Url; use util::geometry::{MAX_RECT, ZERO_POINT}; use util::ipc::OptionalIpcSender; @@ -618,6 +619,9 @@ impl LayoutTask { possibly_locked_rw_data) } Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data), + Msg::AddMetaViewport(translated_rule) => { + self.handle_add_meta_viewport(translated_rule, possibly_locked_rw_data) + } Msg::GetRPC(response_chan) => { response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as Box<LayoutRPC + Send>).unwrap(); @@ -823,6 +827,19 @@ impl LayoutTask { LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); } + fn handle_add_meta_viewport<'a>(&'a self, + translated_rule: ViewportRule, + possibly_locked_rw_data: + &mut Option<MutexGuard<'a, LayoutTaskData>>) + { + let mut rw_data = self.lock_rw_data(possibly_locked_rw_data); + rw_data.stylist.add_stylesheet(Stylesheet { + rules: vec![CSSRule::Viewport(translated_rule)], + origin: Origin::Author + }); + LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); + } + /// Sets quirks mode for the document, causing the quirks mode stylesheet to be loaded. fn handle_set_quirks_mode<'a>(&'a self, possibly_locked_rw_data: @@ -1657,4 +1674,3 @@ fn get_root_flow_background_color(flow: &mut Flow) -> AzColor { .resolve_color(kid_block_flow.fragment.style.get_background().background_color) .to_gfx_color() } - diff --git a/components/script/dom/htmlmetaelement.rs b/components/script/dom/htmlmetaelement.rs index 1bf78c66c8e..d7f4328bbfc 100644 --- a/components/script/dom/htmlmetaelement.rs +++ b/components/script/dom/htmlmetaelement.rs @@ -5,13 +5,18 @@ use dom::bindings::codegen::Bindings::HTMLMetaElementBinding; use dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods; use dom::bindings::codegen::InheritTypes::HTMLMetaElementDerived; -use dom::bindings::js::Root; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::js::{Root, RootedReference}; use dom::document::Document; use dom::element::ElementTypeId; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; -use dom::node::{Node, NodeTypeId}; -use util::str::DOMString; +use dom::node::{Node, NodeTypeId, window_from_node}; +use dom::virtualmethods::VirtualMethods; +use layout_interface::{LayoutChan, Msg}; +use std::ascii::AsciiExt; +use style::viewport::ViewportRule; +use util::str::{DOMString, HTML_SPACE_CHARACTERS}; #[dom_struct] pub struct HTMLMetaElement { @@ -42,6 +47,35 @@ impl HTMLMetaElement { let element = HTMLMetaElement::new_inherited(localName, prefix, document); Node::reflect_node(box element, document, HTMLMetaElementBinding::Wrap) } + + fn process_attributes(&self) { + let element = ElementCast::from_ref(self); + if let Some(name) = element.get_attribute(&ns!(""), &atom!("name")).r() { + let name = name.value().to_ascii_lowercase(); + let name = name.trim_matches(HTML_SPACE_CHARACTERS); + + match name { + "viewport" => self.translate_viewport(), + _ => {} + } + } + } + + fn translate_viewport(&self) { + let element = ElementCast::from_ref(self); + if let Some(content) = element.get_attribute(&ns!(""), &atom!("content")).r() { + let content = content.value(); + if !content.is_empty() { + if let Some(translated_rule) = ViewportRule::from_meta(&**content) { + let node = NodeCast::from_ref(self); + let win = window_from_node(node); + let LayoutChan(ref layout_chan) = win.r().layout_chan(); + + layout_chan.send(Msg::AddMetaViewport(translated_rule)).unwrap(); + } + } + } + } } impl HTMLMetaElementMethods for HTMLMetaElement { @@ -57,3 +91,20 @@ impl HTMLMetaElementMethods for HTMLMetaElement { // https://html.spec.whatwg.org/multipage/#dom-meta-content make_setter!(SetContent, "content"); } + +impl VirtualMethods for HTMLMetaElement { + fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> { + let htmlelement: &HTMLElement = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.bind_to_tree(tree_in_doc); + } + + if tree_in_doc { + self.process_attributes(); + } + } +} diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 137843fb5ff..639b1ebd756 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -20,6 +20,7 @@ use dom::bindings::codegen::InheritTypes::HTMLIFrameElementCast; use dom::bindings::codegen::InheritTypes::HTMLImageElementCast; use dom::bindings::codegen::InheritTypes::HTMLInputElementCast; use dom::bindings::codegen::InheritTypes::HTMLLinkElementCast; +use dom::bindings::codegen::InheritTypes::HTMLMetaElementCast; use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast; use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementCast; use dom::bindings::codegen::InheritTypes::HTMLOptionElementCast; @@ -179,6 +180,10 @@ pub fn vtable_for<'a>(node: &'a Node) -> &'a (VirtualMethods + 'a) { let element = HTMLLinkElementCast::to_ref(node).unwrap(); element as &'a (VirtualMethods + 'a) } + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMetaElement)) => { + let element = HTMLMetaElementCast::to_ref(node).unwrap(); + element as &'a (VirtualMethods + 'a) + } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { let element = HTMLObjectElementCast::to_ref(node).unwrap(); element as &'a (VirtualMethods + 'a) diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index 230ebe79692..9647ddd4376 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -28,6 +28,7 @@ use string_cache::Atom; use style::animation::PropertyAnimation; use style::media_queries::MediaQueryList; use style::stylesheets::Stylesheet; +use style::viewport::ViewportRule; use url::Url; pub use dom::node::TrustedNodeAddress; @@ -39,6 +40,9 @@ pub enum Msg { /// Adds the given stylesheet to the document. LoadStylesheet(Url, MediaQueryList, PendingAsyncLoad, Box<StylesheetLoadResponder + Send>), + /// Adds a @viewport rule (translated from a <META name="viewport"> element) to the document. + AddMetaViewport(ViewportRule), + /// Puts a document into quirks mode, causing the quirks mode stylesheet to be loaded. SetQuirksMode, diff --git a/components/style/viewport.rs b/components/style/viewport.rs index f5bfd66e06d..3d3935ecb23 100644 --- a/components/style/viewport.rs +++ b/components/style/viewport.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::Au; +use cssparser::ToCss; use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important}; use euclid::scale_factor::ScaleFactor; use euclid::size::{Size2D, TypedSize2D}; @@ -10,20 +11,23 @@ use parser::{ParserContext, log_css_error}; use properties::longhands; use std::ascii::AsciiExt; use std::collections::hash_map::{Entry, HashMap}; +use std::fmt; use std::intrinsics; +use std::iter::Enumerate; +use std::str::Chars; use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom}; use stylesheets::Origin; use util::geometry::ViewportPx; use values::computed::{Context, ToComputedValue}; -use values::specified::LengthOrPercentageOrAuto; +use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength}; #[derive(Copy, Clone, Debug, PartialEq)] pub enum ViewportDescriptor { - MinWidth(LengthOrPercentageOrAuto), - MaxWidth(LengthOrPercentageOrAuto), + MinWidth(ViewportLength), + MaxWidth(ViewportLength), - MinHeight(LengthOrPercentageOrAuto), - MaxHeight(LengthOrPercentageOrAuto), + MinHeight(ViewportLength), + MaxHeight(ViewportLength), Zoom(Zoom), MinZoom(Zoom), @@ -33,6 +37,98 @@ pub enum ViewportDescriptor { 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> { context: &'a ParserContext<'b> } @@ -57,10 +153,10 @@ impl ViewportDescriptorDeclaration { } } -fn parse_shorthand(input: &mut Parser) -> Result<[LengthOrPercentageOrAuto; 2], ()> { - let min = try!(LengthOrPercentageOrAuto::parse_non_negative(input)); - match input.try(|input| LengthOrPercentageOrAuto::parse_non_negative(input)) { - Err(()) => Ok([min.clone(), min]), +fn parse_shorthand(input: &mut Parser) -> Result<[ViewportLength; 2], ()> { + let min = try!(ViewportLength::parse(input)); + match input.try(|input| ViewportLength::parse(input)) { + Err(()) => Ok([min, min]), Ok(max) => Ok([min, max]) } } @@ -102,16 +198,16 @@ impl<'a, 'b> DeclarationParser for ViewportRuleParser<'a, 'b> { match name { 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") => - ok!(MaxWidth(LengthOrPercentageOrAuto::parse_non_negative)), + ok!(MaxWidth(ViewportLength::parse)), n if n.eq_ignore_ascii_case("width") => ok!(shorthand -> [MinWidth, MaxWidth]), 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") => - ok!(MaxHeight(LengthOrPercentageOrAuto::parse_non_negative)), + ok!(MaxHeight(ViewportLength::parse)), n if n.eq_ignore_ascii_case("height") => ok!(shorthand -> [MinHeight, MaxHeight]), @@ -137,6 +233,19 @@ pub struct ViewportRule { 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 { pub fn parse(input: &mut Parser, context: &ParserContext) -> Result<ViewportRule, ()> @@ -166,6 +275,145 @@ impl ViewportRule { 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 { @@ -306,9 +554,9 @@ impl MaybeNew for ViewportConstraints { ($op:ident, $opta:expr, $optb:expr) => { match ($opta, $optb) { (None, None) => None, - (a, None) => a.clone(), - (None, b) => b.clone(), - (a, b) => Some(a.clone().unwrap().$op(b.clone().unwrap())), + (a, None) => a, + (None, b) => b, + (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 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 @@ -363,18 +611,41 @@ impl MaybeNew for ViewportConstraints { 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 { - ($value:ident, $dimension:ident) => { + ($value:ident, $dimension:ident, $extend_to:ident => $auto_extend_to:expr) => { if let Some($value) = $value { match $value { - LengthOrPercentageOrAuto::Length(value) => - Some(value.to_computed_value(&context)), - LengthOrPercentageOrAuto::Percentage(value) => - Some(initial_viewport.$dimension.scale_by(value.0)), - LengthOrPercentageOrAuto::Auto => None, - LengthOrPercentageOrAuto::Calc(calc) => { - let calc = calc.to_computed_value(&context); - Some(initial_viewport.$dimension.scale_by(calc.percentage()) + calc.length()) + ViewportLength::Specified(length) => match length { + LengthOrPercentageOrAuto::Length(value) => + Some(value.to_computed_value(&context)), + LengthOrPercentageOrAuto::Percentage(value) => + Some(initial_viewport.$dimension.scale_by(value.0)), + LengthOrPercentageOrAuto::Auto => None, + LengthOrPercentageOrAuto::Calc(calc) => { + 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 { @@ -383,10 +654,14 @@ impl MaybeNew for ViewportConstraints { } } - let min_width = to_pixel_length!(min_width, width); - let max_width = to_pixel_length!(max_width, width); - let min_height = to_pixel_length!(min_height, height); - let max_height = to_pixel_length!(max_height, height); + // DEVICE-ADAPT § 9.3 states that max-descriptors need to be resolved + // before min-descriptors. + // http://dev.w3.org/csswg/css-device-adapt/#resolve-extend-to-zoom + 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 macro_rules! resolve { @@ -421,7 +696,7 @@ impl MaybeNew for ViewportConstraints { Au(0) => initial_viewport.width, initial_height => { 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) } }); diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 2a413231511..c823dd97d3d 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -397,6 +397,7 @@ flaky_cpu,prefs:"layout.writing-mode.enabled" == vertical-lr-blocks.html vertica == vertical_align_top_a.html vertical_align_top_ref.html == vertical_align_top_bottom_a.html vertical_align_top_bottom_ref.html == vertical_align_top_span_a.html vertical_align_top_span_ref.html +== viewport_meta.html viewport_rule_ref.html resolution=800x600 == viewport_percentage_vmin_vmax.html viewport_percentage_vmin_vmax_a.html # resolution=600x800 == viewport_percentage_vmin_vmax.html viewport_percentage_vmin_vmax_b.html resolution=800x600 == viewport_percentage_vw_vh.html viewport_percentage_vw_vh_a.html diff --git a/tests/ref/viewport_meta.html b/tests/ref/viewport_meta.html new file mode 100644 index 00000000000..5c386bd7d76 --- /dev/null +++ b/tests/ref/viewport_meta.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> + <head> + <meta name="viewport" content="width=240"> + <style> + #container { + background: blue; + height: 100vh; + width: 100vw; + } + + #box { + background: green; + height: 50vh; + width: 50vw; + } + </style> + </head> + <body> + <div id="container"> + <div id="box"> + </div> + </div> + </body> +</html> diff --git a/tests/unit/style/lib.rs b/tests/unit/style/lib.rs index 3c5d3a21004..e3b6684f4b9 100644 --- a/tests/unit/style/lib.rs +++ b/tests/unit/style/lib.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #![feature(plugin)] +#![cfg_attr(test, feature(core_intrinsics))] #![plugin(string_cache_plugin)] extern crate app_units; diff --git a/tests/unit/style/viewport.rs b/tests/unit/style/viewport.rs index bd1a2c0195a..31ae0a9e6c2 100644 --- a/tests/unit/style/viewport.rs +++ b/tests/unit/style/viewport.rs @@ -8,7 +8,9 @@ use euclid::size::Size2D; use style::media_queries::{Device, MediaType}; use style::parser::ParserContext; 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_traits::viewport::*; use url::Url; @@ -38,6 +40,26 @@ fn test_viewport_rule<F>(css: &str, 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 { ($declarations:ident == 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] fn empty_viewport_rule() { let device = Device::new(MediaType::Screen, Size2D::typed(800., 600.)); @@ -84,10 +115,10 @@ fn simple_viewport_rules() { &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 9); - assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto); - assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); - assert_decl_eq!(&declarations[2], Author, MinHeight: LengthOrPercentageOrAuto::Auto); - assert_decl_eq!(&declarations[3], Author, MaxHeight: LengthOrPercentageOrAuto::Auto); + 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.)); @@ -100,10 +131,49 @@ fn simple_viewport_rules() { &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 4); - assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(200.))); - assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); - assert_decl_eq!(&declarations[2], Author, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.))); - assert_decl_eq!(&declarations[3], Author, MaxHeight: LengthOrPercentageOrAuto::Auto); + 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); }); } @@ -116,7 +186,7 @@ fn cascading_within_viewport_rule() { &device, |declarations, css| { println!("{}", css); 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 @@ -124,7 +194,7 @@ fn cascading_within_viewport_rule() { &device, |declarations, css| { println!("{}", css); 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 @@ -132,7 +202,7 @@ fn cascading_within_viewport_rule() { &device, |declarations, css| { println!("{}", css); 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 @@ -140,8 +210,8 @@ fn cascading_within_viewport_rule() { &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); - assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto); - assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); + 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 @@ -149,8 +219,8 @@ fn cascading_within_viewport_rule() { &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); - assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto); - assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto); + 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 @@ -158,8 +228,8 @@ fn cascading_within_viewport_rule() { &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); - assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important); - assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Length(Length::from_px(200.))); + 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 @@ -167,8 +237,8 @@ fn cascading_within_viewport_rule() { &device, |declarations, css| { println!("{}", css); assert_decl_len!(declarations == 2); - assert_decl_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important); - assert_decl_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto, !important); + assert_decl_eq!(&declarations[0], Author, MinWidth: ViewportLength::Specified(Auto), !important); + assert_decl_eq!(&declarations[1], Author, MaxWidth: ViewportLength::Specified(Auto), !important); }); } @@ -189,8 +259,8 @@ fn multiple_stylesheets_cascading() { .declarations; assert_decl_len!(declarations == 3); 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[2], Author, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(300.))); + 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), @@ -203,10 +273,8 @@ fn multiple_stylesheets_cascading() { .cascade() .declarations; assert_decl_len!(declarations == 3); - assert_decl_eq!( - &declarations[0], UserAgent, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(100.)), !important); - assert_decl_eq!( - &declarations[1], User, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.)), !important); + 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); } |