Auto merge of #5361 - luniv:css-device-adapt, r=mbrubeck

Spec: http://dev.w3.org/csswg/css-device-adapt/

Currently, the actual viewport is used by the layout task as part of the reflow, and the compositor uses the zoom constraints. I'm not sure if anywhere else currently needs access to the constraints (i.e. there's no CSSOM as far as I can tell).

I did not implement sections 9 (viewport <META>) or 10 (handling 'auto' for 'zoom').

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/5361)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2015-05-06 12:41:09 -05:00
commit ccf1e6b9a7
24 changed files with 1030 additions and 71 deletions

View file

@ -28,6 +28,9 @@ path = "../profile_traits"
[dependencies.net_traits]
path = "../net_traits"
[dependencies.style]
path = "../style"
[dependencies.util]
path = "../util"
@ -62,6 +65,7 @@ git = "https://github.com/servo/gleam"
git = "https://github.com/servo/rust-x11-clipboard"
[dependencies]
num = "0.1.24"
url = "0.2.16"
time = "0.1.17"
libc = "*"

View file

@ -43,6 +43,7 @@ use std::mem as std_mem;
use std::rc::Rc;
use std::slice::bytes::copy_memory;
use std::sync::mpsc::Sender;
use style::viewport::ViewportConstraints;
use time::{precise_time_ns, precise_time_s};
use url::Url;
use util::geometry::{PagePx, ScreenPx, ViewportPx};
@ -75,6 +76,10 @@ pub struct IOCompositor<Window: WindowMethods> {
/// "Mobile-style" zoom that does not reflow the page.
viewport_zoom: ScaleFactor<PagePx, ViewportPx, f32>,
/// Viewport zoom constraints provided by @viewport.
min_viewport_zoom: Option<ScaleFactor<PagePx, ViewportPx, f32>>,
max_viewport_zoom: Option<ScaleFactor<PagePx, ViewportPx, f32>>,
/// "Desktop-style" zoom that resizes the viewport to fit the window.
/// See `ViewportPx` docs in util/geom.rs for details.
page_zoom: ScaleFactor<ViewportPx, ScreenPx, f32>,
@ -219,6 +224,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
shutdown_state: ShutdownState::NotShuttingDown,
page_zoom: ScaleFactor::new(1.0),
viewport_zoom: ScaleFactor::new(1.0),
min_viewport_zoom: None,
max_viewport_zoom: None,
zoom_action: false,
zoom_time: 0f64,
got_load_complete_message: false,
@ -389,6 +396,10 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
}
(Msg::ViewportConstrained(pipeline_id, constraints), ShutdownState::NotShuttingDown) => {
self.constrain_viewport(pipeline_id, constraints);
}
// When we are shutting_down, we need to avoid performing operations
// such as Paint that may crash because we have begun tearing down
// the rest of our resources.
@ -945,6 +956,21 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
}
fn constrain_viewport(&mut self, pipeline_id: PipelineId, constraints: ViewportConstraints) {
let is_root = self.root_pipeline.as_ref().map_or(false, |root_pipeline| {
root_pipeline.id == pipeline_id
});
if is_root {
// TODO: actual viewport size
self.viewport_zoom = constraints.initial_zoom;
self.min_viewport_zoom = constraints.min_zoom;
self.max_viewport_zoom = constraints.max_zoom;
self.update_zoom_transform();
}
}
fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
match opts::get().device_pixels_per_px {
Some(device_pixels_per_px) => device_pixels_per_px,
@ -976,12 +1002,19 @@ impl<Window: WindowMethods> IOCompositor<Window> {
// TODO(pcwalton): I think this should go through the same queuing as scroll events do.
fn on_pinch_zoom_window_event(&mut self, magnification: f32) {
use num::Float;
self.zoom_action = true;
self.zoom_time = precise_time_s();
let old_viewport_zoom = self.viewport_zoom;
self.viewport_zoom = ScaleFactor::new((self.viewport_zoom.get() * magnification).max(1.0));
let viewport_zoom = self.viewport_zoom;
let mut viewport_zoom = self.viewport_zoom.get() * magnification;
if let Some(min_zoom) = self.min_viewport_zoom.as_ref() {
viewport_zoom = min_zoom.get().max(viewport_zoom)
}
let viewport_zoom = self.max_viewport_zoom.as_ref().map_or(1., |z| z.get()).min(viewport_zoom);
let viewport_zoom = ScaleFactor::new(viewport_zoom);
self.viewport_zoom = viewport_zoom;
self.update_zoom_transform();
@ -1452,4 +1485,3 @@ pub enum CompositingReason {
/// The window has been zoomed.
Zoom,
}

View file

@ -26,6 +26,7 @@ use profile_traits::time;
use std::sync::mpsc::{channel, Sender, Receiver};
use std::fmt::{Error, Formatter, Debug};
use std::rc::Rc;
use style::viewport::ViewportConstraints;
use url::Url;
use util::cursor::Cursor;
@ -219,6 +220,8 @@ pub enum Msg {
SetCursor(Cursor),
/// Informs the compositor that the paint task for the given pipeline has exited.
PaintTaskExited(PipelineId),
/// Alerts the compositor that the viewport has been constrained in some manner
ViewportConstrained(PipelineId, ViewportConstraints),
}
impl Debug for Msg {
@ -245,6 +248,7 @@ impl Debug for Msg {
Msg::KeyEvent(..) => write!(f, "KeyEvent"),
Msg::SetCursor(..) => write!(f, "SetCursor"),
Msg::PaintTaskExited(..) => write!(f, "PaintTaskExited"),
Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"),
}
}
}
@ -303,4 +307,3 @@ pub trait CompositorEventListener {
/// Requests that the compositor send the title for the main frame as soon as possible.
fn get_title_for_main_frame(&self);
}

View file

@ -34,6 +34,7 @@ use std::io::{self, Write};
use std::marker::PhantomData;
use std::mem::replace;
use std::sync::mpsc::{Sender, Receiver, channel};
use style::viewport::ViewportConstraints;
use url::Url;
use util::cursor::Cursor;
use util::geometry::PagePx;
@ -418,6 +419,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
self.handle_webdriver_command_msg(pipeline_id,
command);
}
ConstellationMsg::ViewportConstrained(pipeline_id, constraints) => {
debug!("constellation got viewport-constrained event message");
self.handle_viewport_constrained_msg(pipeline_id, constraints);
}
}
true
}
@ -913,6 +918,11 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
self.window_size = new_size;
}
/// Handle updating actual viewport / zoom due to @viewport rules
fn handle_viewport_constrained_msg(&mut self, pipeline_id: PipelineId, constraints: ViewportConstraints) {
self.compositor_proxy.send(CompositorMsg::ViewportConstrained(pipeline_id, constraints));
}
// Close a frame (and all children)
fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) {
let frame = self.frames.remove(&frame_id).unwrap();

View file

@ -107,8 +107,9 @@ impl CompositorEventListener for NullCompositor {
Msg::ChangePageTitle(..) |
Msg::ChangePageUrl(..) |
Msg::KeyEvent(..) |
Msg::SetCursor(..) => {}
Msg::PaintTaskExited(..) => {}
Msg::SetCursor(..) |
Msg::PaintTaskExited(..) |
Msg::ViewportConstrained(..) => {}
}
true
}

View file

@ -19,8 +19,10 @@ extern crate png;
extern crate script_traits;
extern crate msg;
extern crate net;
extern crate num;
extern crate profile_traits;
extern crate net_traits;
extern crate style;
#[macro_use]
extern crate util;
extern crate gleam;

View file

@ -847,19 +847,32 @@ impl LayoutTask {
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
// TODO: Calculate the "actual viewport":
// http://www.w3.org/TR/css-device-adapt/#actual-viewport
let viewport_size = data.window_size.initial_viewport;
let initial_viewport = data.window_size.initial_viewport;
let old_screen_size = rw_data.screen_size;
let current_screen_size = Size2D(Au::from_f32_px(viewport_size.width.get()),
Au::from_f32_px(viewport_size.height.get()));
let current_screen_size = Size2D(Au::from_f32_px(initial_viewport.width.get()),
Au::from_f32_px(initial_viewport.height.get()));
rw_data.screen_size = current_screen_size;
// Handle conditions where the entire flow tree is invalid.
let screen_size_changed = current_screen_size != old_screen_size;
if screen_size_changed {
let device = Device::new(MediaType::Screen, data.window_size.initial_viewport);
// Calculate the actual viewport as per DEVICE-ADAPT § 6
let device = Device::new(MediaType::Screen, initial_viewport);
rw_data.stylist.set_device(device);
if let Some(constraints) = rw_data.stylist.constrain_viewport() {
debug!("Viewport constraints: {:?}", constraints);
// other rules are evaluated against the actual viewport
rw_data.screen_size = Size2D(Au::from_f32_px(constraints.size.width.get()),
Au::from_f32_px(constraints.size.height.get()));
let device = Device::new(MediaType::Screen, constraints.size);
rw_data.stylist.set_device(device);
// let the constellation know about the viewport constraints
let ConstellationChan(ref constellation_chan) = rw_data.constellation_chan;
constellation_chan.send(ConstellationMsg::ViewportConstrained(self.id, constraints)).unwrap();
}
}
// If the entire flow tree is invalid, then it will be reflowed anyhow.

View file

@ -14,6 +14,7 @@ use layers::geometry::DevicePixel;
use util::cursor::Cursor;
use util::geometry::{PagePx, ViewportPx};
use std::sync::mpsc::{channel, Sender, Receiver};
use style::viewport::ViewportConstraints;
use webdriver_traits::WebDriverScriptCommand;
use url::Url;
@ -233,7 +234,9 @@ pub enum Msg {
/// Requests that the constellation retrieve the current contents of the clipboard
GetClipboardContents(Sender<String>),
// Dispatch a webdriver command
WebDriverCommand(PipelineId, WebDriverScriptCommand)
WebDriverCommand(PipelineId, WebDriverScriptCommand),
/// Notifies the constellation that the viewport has been constrained in some manner
ViewportConstrained(PipelineId, ViewportConstraints),
}
#[derive(Clone, Eq, PartialEq)]

View file

@ -9,6 +9,7 @@ extern crate hyper;
extern crate layers;
extern crate util;
extern crate url;
extern crate style;
extern crate webdriver_traits;
#[cfg(target_os="macos")]

View file

@ -123,9 +123,11 @@ dependencies = [
"msg 0.0.1",
"net 0.0.1",
"net_traits 0.0.1",
"num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"profile_traits 0.0.1",
"script_traits 0.0.1",
"style 0.0.1",
"time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
@ -1120,6 +1122,7 @@ dependencies = [
"lazy_static 0.1.10 (git+https://github.com/Kimundi/lazy-static.rs)",
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
"plugins 0.0.1",
"rustc-serialize 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.1.0 (git+https://github.com/servo/rust-selectors)",

View file

@ -38,3 +38,4 @@ url = "0.2.16"
mod_path = "0.1"
bitflags = "*"
cssparser = "0.3.1"
num = "0.1.24"

View file

@ -34,6 +34,7 @@ extern crate selectors;
#[macro_use]
extern crate lazy_static;
extern crate num;
extern crate util;
@ -50,6 +51,7 @@ pub mod media_queries;
pub mod font_face;
pub mod legacy;
pub mod animation;
pub mod viewport;
macro_rules! reexport_computed_values {
( $( $name: ident )+ ) => {
@ -63,4 +65,3 @@ macro_rules! reexport_computed_values {
}
}
longhand_properties_idents!(reexport_computed_values);

View file

@ -11,6 +11,7 @@ use log;
use stylesheets::Origin;
pub struct ParserContext<'a> {
pub stylesheet_origin: Origin,
pub base_url: &'a Url,
pub selector_context: SelectorParserContext,
}
@ -20,6 +21,7 @@ impl<'a> ParserContext<'a> {
let mut selector_context = SelectorParserContext::new();
selector_context.in_user_agent_stylesheet = stylesheet_origin == Origin::UserAgent;
ParserContext {
stylesheet_origin: stylesheet_origin,
base_url: base_url,
selector_context: selector_context,
}

View file

@ -17,6 +17,7 @@ use media_queries::Device;
use node::TElementAttributes;
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use stylesheets::{Stylesheet, CSSRuleIteratorExt, Origin};
use viewport::{ViewportConstraints, ViewportRuleCascade};
pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
@ -69,6 +70,14 @@ impl Stylist {
stylist
}
pub fn constrain_viewport(&self) -> Option<ViewportConstraints> {
let cascaded_rule = self.stylesheets.iter()
.flat_map(|s| s.effective_rules(&self.device).viewport())
.cascade();
ViewportConstraints::maybe_new(self.device.viewport_size, &cascaded_rule)
}
pub fn update(&mut self) -> bool {
if self.is_dirty {
self.element_map = PerPseudoElementSelectorMap::new();

View file

@ -19,6 +19,7 @@ use properties::{PropertyDeclarationBlock, parse_property_declaration_list};
use media_queries::{Device, MediaQueryList, parse_media_query_list};
use font_face::{FontFaceRule, parse_font_face_block};
use util::smallvec::SmallVec2;
use viewport::ViewportRule;
/// Each style rule has an origin, which determines where it enters the cascade.
@ -53,6 +54,7 @@ pub enum CSSRule {
Style(StyleRule),
Media(MediaRule),
FontFace(FontFaceRule),
Viewport(ViewportRule),
}
#[derive(Debug, PartialEq)]
@ -216,6 +218,7 @@ pub mod rule_filter {
use std::marker::PhantomData;
use super::{CSSRule, MediaRule, StyleRule};
use super::super::font_face::FontFaceRule;
use super::super::viewport::ViewportRule;
macro_rules! rule_filter {
($variant:ident -> $value:ty) => {
@ -259,6 +262,7 @@ pub mod rule_filter {
rule_filter!(FontFace -> FontFaceRule);
rule_filter!(Media -> MediaRule);
rule_filter!(Style -> StyleRule);
rule_filter!(Viewport -> ViewportRule);
}
/// Extension methods for `CSSRule` iterators.
@ -271,6 +275,9 @@ pub trait CSSRuleIteratorExt<'a>: Iterator<Item=&'a CSSRule> {
/// Yield only style rules.
fn style(self) -> rule_filter::Style<'a, Self>;
/// Yield only @viewport rules.
fn viewport(self) -> rule_filter::Viewport<'a, Self>;
}
impl<'a, I> CSSRuleIteratorExt<'a> for I where I: Iterator<Item=&'a CSSRule> {
@ -288,6 +295,11 @@ impl<'a, I> CSSRuleIteratorExt<'a> for I where I: Iterator<Item=&'a CSSRule> {
fn style(self) -> rule_filter::Style<'a, I> {
rule_filter::Style::new(self)
}
#[inline]
fn viewport(self) -> rule_filter::Viewport<'a, I> {
rule_filter::Viewport::new(self)
}
}
fn parse_nested_rules(context: &ParserContext, input: &mut Parser) -> Vec<CSSRule> {
@ -324,6 +336,7 @@ enum State {
enum AtRulePrelude {
FontFace,
Media(MediaQueryList),
Viewport,
}
@ -414,6 +427,13 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
},
"font-face" => {
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace))
},
"viewport" => {
if ::util::opts::experimental_enabled() {
Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
} else {
Err(())
}
}
_ => Err(())
}
@ -430,6 +450,9 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
rules: parse_nested_rules(self.context, input),
}))
}
AtRulePrelude::Viewport => {
ViewportRule::parse(input, self.context).map(CSSRule::Viewport)
}
}
}
}

View file

@ -86,6 +86,22 @@ pub mod specified {
use util::geometry::Au;
use super::CSSFloat;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AllowedNumericType {
All,
NonNegative
}
impl AllowedNumericType {
#[inline]
pub fn is_ok(&self, value: f32) -> bool {
match self {
&AllowedNumericType::All => true,
&AllowedNumericType::NonNegative => value >= 0.,
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct CSSColor {
pub parsed: cssparser::Color,
@ -294,21 +310,21 @@ pub mod specified {
const AU_PER_PC: CSSFloat = AU_PER_PT * 12.;
impl Length {
#[inline]
fn parse_internal(input: &mut Parser, negative_ok: bool) -> Result<Length, ()> {
fn parse_internal(input: &mut Parser, context: &AllowedNumericType) -> Result<Length, ()> {
match try!(input.next()) {
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
Length::parse_dimension(value.value, unit)
}
Token::Number(ref value) if value.value == 0. => Ok(Length::Absolute(Au(0))),
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) =>
Length::parse_dimension(value.value, unit),
Token::Number(ref value) if value.value == 0. =>
Ok(Length::Absolute(Au(0))),
_ => Err(())
}
}
#[allow(dead_code)]
pub fn parse(input: &mut Parser) -> Result<Length, ()> {
Length::parse_internal(input, /* negative_ok = */ true)
Length::parse_internal(input, &AllowedNumericType::All)
}
pub fn parse_non_negative(input: &mut Parser) -> Result<Length, ()> {
Length::parse_internal(input, /* negative_ok = */ false)
Length::parse_internal(input, &AllowedNumericType::NonNegative)
}
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Length, ()> {
match_ignore_ascii_case! { unit,
@ -353,30 +369,27 @@ pub mod specified {
}
}
impl LengthOrPercentage {
fn parse_internal(input: &mut Parser, negative_ok: bool)
-> Result<LengthOrPercentage, ()> {
fn parse_internal(input: &mut Parser, context: &AllowedNumericType)
-> Result<LengthOrPercentage, ()>
{
match try!(input.next()) {
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
Length::parse_dimension(value.value, unit)
.map(LengthOrPercentage::Length)
}
Token::Percentage(ref value) if negative_ok || value.unit_value >= 0. => {
Ok(LengthOrPercentage::Percentage(value.unit_value))
}
Token::Number(ref value) if value.value == 0. => {
Ok(LengthOrPercentage::Length(Length::Absolute(Au(0))))
}
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) =>
Length::parse_dimension(value.value, unit).map(LengthOrPercentage::Length),
Token::Percentage(ref value) if context.is_ok(value.unit_value) =>
Ok(LengthOrPercentage::Percentage(value.unit_value)),
Token::Number(ref value) if value.value == 0. =>
Ok(LengthOrPercentage::Length(Length::Absolute(Au(0)))),
_ => Err(())
}
}
#[allow(dead_code)]
#[inline]
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentage, ()> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
LengthOrPercentage::parse_internal(input, &AllowedNumericType::All)
}
#[inline]
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentage, ()> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
LengthOrPercentage::parse_internal(input, &AllowedNumericType::NonNegative)
}
}
@ -397,33 +410,30 @@ pub mod specified {
}
}
}
impl LengthOrPercentageOrAuto {
fn parse_internal(input: &mut Parser, negative_ok: bool)
-> Result<LengthOrPercentageOrAuto, ()> {
fn parse_internal(input: &mut Parser, context: &AllowedNumericType)
-> Result<LengthOrPercentageOrAuto, ()>
{
match try!(input.next()) {
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
Length::parse_dimension(value.value, unit)
.map(LengthOrPercentageOrAuto::Length)
}
Token::Percentage(ref value) if negative_ok || value.unit_value >= 0. => {
Ok(LengthOrPercentageOrAuto::Percentage(value.unit_value))
}
Token::Number(ref value) if value.value == 0. => {
Ok(LengthOrPercentageOrAuto::Length(Length::Absolute(Au(0))))
}
Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") => {
Ok(LengthOrPercentageOrAuto::Auto)
}
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) =>
Length::parse_dimension(value.value, unit).map(LengthOrPercentageOrAuto::Length),
Token::Percentage(ref value) if context.is_ok(value.unit_value) =>
Ok(LengthOrPercentageOrAuto::Percentage(value.unit_value)),
Token::Number(ref value) if value.value == 0. =>
Ok(LengthOrPercentageOrAuto::Length(Length::Absolute(Au(0)))),
Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") =>
Ok(LengthOrPercentageOrAuto::Auto),
_ => Err(())
}
}
#[inline]
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
LengthOrPercentageOrAuto::parse_internal(input, &AllowedNumericType::All)
}
#[inline]
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
LengthOrPercentageOrAuto::parse_internal(input, &AllowedNumericType::NonNegative)
}
}
@ -438,40 +448,36 @@ pub mod specified {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match self {
&LengthOrPercentageOrNone::Length(length) => length.to_css(dest),
&LengthOrPercentageOrNone::Percentage(percentage)
=> write!(dest, "{}%", percentage * 100.),
&LengthOrPercentageOrNone::Percentage(percentage) =>
write!(dest, "{}%", percentage * 100.),
&LengthOrPercentageOrNone::None => dest.write_str("none"),
}
}
}
impl LengthOrPercentageOrNone {
fn parse_internal(input: &mut Parser, negative_ok: bool)
-> Result<LengthOrPercentageOrNone, ()> {
fn parse_internal(input: &mut Parser, context: &AllowedNumericType)
-> Result<LengthOrPercentageOrNone, ()>
{
match try!(input.next()) {
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
Length::parse_dimension(value.value, unit)
.map(LengthOrPercentageOrNone::Length)
}
Token::Percentage(ref value) if negative_ok || value.unit_value >= 0. => {
Ok(LengthOrPercentageOrNone::Percentage(value.unit_value))
}
Token::Number(ref value) if value.value == 0. => {
Ok(LengthOrPercentageOrNone::Length(Length::Absolute(Au(0))))
}
Token::Ident(ref value) if value.eq_ignore_ascii_case("none") => {
Ok(LengthOrPercentageOrNone::None)
}
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) =>
Length::parse_dimension(value.value, unit).map(LengthOrPercentageOrNone::Length),
Token::Percentage(ref value) if context.is_ok(value.unit_value) =>
Ok(LengthOrPercentageOrNone::Percentage(value.unit_value)),
Token::Number(ref value) if value.value == 0. =>
Ok(LengthOrPercentageOrNone::Length(Length::Absolute(Au(0)))),
Token::Ident(ref value) if value.eq_ignore_ascii_case("none") =>
Ok(LengthOrPercentageOrNone::None),
_ => Err(())
}
}
#[allow(dead_code)]
#[inline]
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentageOrNone, ()> {
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ true)
LengthOrPercentageOrNone::parse_internal(input, &AllowedNumericType::All)
}
#[inline]
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentageOrNone, ()> {
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ false)
LengthOrPercentageOrNone::parse_internal(input, &AllowedNumericType::NonNegative)
}
}

View file

@ -0,0 +1,509 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::{Parser, DeclarationListParser, AtRuleParser, DeclarationParser, ToCss, parse_important};
use geom::size::{Size2D, TypedSize2D};
use geom::scale_factor::ScaleFactor;
use parser::{ParserContext, log_css_error};
use properties::longhands;
use stylesheets::Origin;
use util::geometry::{Au, PagePx, ViewportPx};
use values::specified::{AllowedNumericType, Length, LengthOrPercentageOrAuto};
use std::ascii::AsciiExt;
use std::collections::hash_map::{Entry, HashMap};
use std::fmt;
use std::intrinsics;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ViewportDescriptor {
MinWidth(LengthOrPercentageOrAuto),
MaxWidth(LengthOrPercentageOrAuto),
MinHeight(LengthOrPercentageOrAuto),
MaxHeight(LengthOrPercentageOrAuto),
Zoom(Zoom),
MinZoom(Zoom),
MaxZoom(Zoom),
UserZoom(UserZoom),
Orientation(Orientation)
}
/// Zoom is a number | percentage | auto
/// See http://dev.w3.org/csswg/css-device-adapt/#descdef-viewport-zoom
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Zoom {
Number(f32),
Percentage(f32),
Auto,
}
impl ToCss for Zoom {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write
{
match self {
&Zoom::Number(number) => write!(dest, "{}", number),
&Zoom::Percentage(percentage) => write!(dest, "{}%", percentage * 100.),
&Zoom::Auto => write!(dest, "auto")
}
}
}
impl Zoom {
pub fn parse(input: &mut Parser) -> Result<Zoom, ()> {
use cssparser::Token;
match try!(input.next()) {
Token::Percentage(ref value) if AllowedNumericType::NonNegative.is_ok(value.unit_value) =>
Ok(Zoom::Percentage(value.unit_value)),
Token::Number(ref value) if AllowedNumericType::NonNegative.is_ok(value.value) =>
Ok(Zoom::Number(value.value)),
Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") =>
Ok(Zoom::Auto),
_ => Err(())
}
}
#[inline]
pub fn to_f32(&self) -> Option<f32> {
match self {
&Zoom::Number(number) => Some(number as f32),
&Zoom::Percentage(percentage) => Some(percentage as f32),
&Zoom::Auto => None
}
}
}
define_css_keyword_enum!(UserZoom:
"zoom" => Zoom,
"fixed" => Fixed);
define_css_keyword_enum!(Orientation:
"auto" => Auto,
"portrait" => Portrait,
"landscape" => Landscape);
struct ViewportRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ViewportDescriptorDeclaration {
pub origin: Origin,
pub descriptor: ViewportDescriptor,
pub important: bool
}
impl ViewportDescriptorDeclaration {
pub fn new(origin: Origin,
descriptor: ViewportDescriptor,
important: bool) -> ViewportDescriptorDeclaration
{
ViewportDescriptorDeclaration {
origin: origin,
descriptor: descriptor,
important: important
}
}
}
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]),
Ok(max) => Ok([min, max])
}
}
impl<'a, 'b> AtRuleParser for ViewportRuleParser<'a, 'b> {
type Prelude = ();
type AtRule = Vec<ViewportDescriptorDeclaration>;
}
impl<'a, 'b> DeclarationParser for ViewportRuleParser<'a, 'b> {
type Declaration = Vec<ViewportDescriptorDeclaration>;
fn parse_value(&self, name: &str, input: &mut Parser) -> Result<Vec<ViewportDescriptorDeclaration>, ()> {
macro_rules! declaration {
($declaration:ident($parse:path)) => {
declaration!($declaration(value: try!($parse(input)),
important: input.try(parse_important).is_ok()))
};
($declaration:ident(value: $value:expr, important: $important:expr)) => {
ViewportDescriptorDeclaration::new(
self.context.stylesheet_origin,
ViewportDescriptor::$declaration($value),
$important)
}
}
macro_rules! ok {
($declaration:ident($parse:path)) => {
Ok(vec![declaration!($declaration($parse))])
};
(shorthand -> [$min:ident, $max:ident]) => {{
let shorthand = try!(parse_shorthand(input));
let important = input.try(parse_important).is_ok();
Ok(vec![declaration!($min(value: shorthand[0], important: important)),
declaration!($max(value: shorthand[1], important: important))])
}}
}
match name {
n if n.eq_ignore_ascii_case("min-width") =>
ok!(MinWidth(LengthOrPercentageOrAuto::parse_non_negative)),
n if n.eq_ignore_ascii_case("max-width") =>
ok!(MaxWidth(LengthOrPercentageOrAuto::parse_non_negative)),
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)),
n if n.eq_ignore_ascii_case("max-height") =>
ok!(MaxHeight(LengthOrPercentageOrAuto::parse_non_negative)),
n if n.eq_ignore_ascii_case("height") =>
ok!(shorthand -> [MinHeight, MaxHeight]),
n if n.eq_ignore_ascii_case("zoom") =>
ok!(Zoom(Zoom::parse)),
n if n.eq_ignore_ascii_case("min-zoom") =>
ok!(MinZoom(Zoom::parse)),
n if n.eq_ignore_ascii_case("max-zoom") =>
ok!(MaxZoom(Zoom::parse)),
n if n.eq_ignore_ascii_case("user-zoom") =>
ok!(UserZoom(UserZoom::parse)),
n if n.eq_ignore_ascii_case("orientation") =>
ok!(Orientation(Orientation::parse)),
_ => Err(()),
}
}
}
#[derive(Debug, PartialEq)]
pub struct ViewportRule {
pub declarations: Vec<ViewportDescriptorDeclaration>
}
impl ViewportRule {
pub fn parse<'a>(input: &mut Parser, context: &'a ParserContext)
-> Result<ViewportRule, ()>
{
let parser = ViewportRuleParser { context: context };
let mut errors = vec![];
let valid_declarations = DeclarationListParser::new(input, parser)
.filter_map(|result| {
match result {
Ok(declarations) => Some(declarations),
Err(range) => {
errors.push(range);
None
}
}
})
.flat_map(|declarations| declarations.into_iter())
.collect::<Vec<_>>();
for range in errors {
let pos = range.start;
let message = format!("Unsupported @viewport descriptor declaration: '{}'",
input.slice(range));
log_css_error(input, pos, &*message);
}
Ok(ViewportRule { declarations: valid_declarations.iter().cascade() })
}
}
pub trait ViewportRuleCascade: Iterator + Sized {
fn cascade(self) -> ViewportRule;
}
impl<'a, I> ViewportRuleCascade for I
where I: Iterator<Item=&'a ViewportRule>
{
#[inline]
fn cascade(self) -> ViewportRule {
ViewportRule {
declarations: self.flat_map(|r| r.declarations.iter()).cascade()
}
}
}
trait ViewportDescriptorDeclarationCascade: Iterator + Sized {
fn cascade(self) -> Vec<ViewportDescriptorDeclaration>;
}
/// Computes the cascade precedence as according to
/// http://dev.w3.org/csswg/css-cascade/#cascade-origin
fn cascade_precendence(origin: Origin, important: bool) -> u8 {
match (origin, important) {
(Origin::UserAgent, true) => 1,
(Origin::User, true) => 2,
(Origin::Author, true) => 3,
(Origin::Author, false) => 4,
(Origin::User, false) => 5,
(Origin::UserAgent, false) => 6,
}
}
impl ViewportDescriptorDeclaration {
fn higher_or_equal_precendence(&self, other: &ViewportDescriptorDeclaration) -> bool {
let self_precedence = cascade_precendence(self.origin, self.important);
let other_precedence = cascade_precendence(other.origin, other.important);
self_precedence <= other_precedence
}
}
fn cascade<'a, I>(iter: I) -> Vec<ViewportDescriptorDeclaration>
where I: Iterator<Item=&'a ViewportDescriptorDeclaration>
{
let mut declarations: HashMap<u64, (usize, &'a ViewportDescriptorDeclaration)> = HashMap::new();
// index is used to reconstruct order of appearance after all declarations
// have been added to the map
let mut index = 0;
for declaration in iter {
let descriptor = unsafe {
intrinsics::discriminant_value(&declaration.descriptor)
};
match declarations.entry(descriptor) {
Entry::Occupied(mut entry) => {
if declaration.higher_or_equal_precendence(entry.get().1) {
entry.insert((index, declaration));
index += 1;
}
}
Entry::Vacant(entry) => {
entry.insert((index, declaration));
index += 1;
}
}
}
// convert to a list and sort the descriptors by order of appearance
let mut declarations: Vec<_> = declarations.into_iter().map(|kv| kv.1).collect();
declarations.sort_by(|a, b| a.0.cmp(&b.0));
declarations.into_iter().map(|id| *id.1).collect::<Vec<_>>()
}
impl<'a, I> ViewportDescriptorDeclarationCascade for I
where I: Iterator<Item=&'a ViewportDescriptorDeclaration>
{
#[inline]
fn cascade(self) -> Vec<ViewportDescriptorDeclaration> {
cascade(self)
}
}
#[derive(Debug, PartialEq)]
pub struct ViewportConstraints {
pub size: TypedSize2D<ViewportPx, f32>,
pub initial_zoom: ScaleFactor<PagePx, ViewportPx, f32>,
pub min_zoom: Option<ScaleFactor<PagePx, ViewportPx, f32>>,
pub max_zoom: Option<ScaleFactor<PagePx, ViewportPx, f32>>,
pub user_zoom: UserZoom,
pub orientation: Orientation
}
impl ToCss for ViewportConstraints {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write
{
try!(write!(dest, "@viewport {{"));
try!(write!(dest, " width: {}px;", self.size.width.get()));
try!(write!(dest, " height: {}px;", self.size.height.get()));
try!(write!(dest, " zoom: {};", self.initial_zoom.get()));
if let Some(min_zoom) = self.min_zoom {
try!(write!(dest, " min-zoom: {};", min_zoom.get()));
}
if let Some(max_zoom) = self.max_zoom {
try!(write!(dest, " max-zoom: {};", max_zoom.get()));
}
try!(write!(dest, " user-zoom: ")); try!(self.user_zoom.to_css(dest));
try!(write!(dest, "; orientation: ")); try!(self.orientation.to_css(dest));
write!(dest, "; }}")
}
}
impl ViewportConstraints {
pub fn maybe_new(initial_viewport: TypedSize2D<ViewportPx, f32>,
rule: &ViewportRule)
-> Option<ViewportConstraints>
{
use std::cmp;
use num::{Float, ToPrimitive};
if rule.declarations.is_empty() {
return None
}
let mut min_width = None;
let mut max_width = None;
let mut min_height = None;
let mut max_height = None;
let mut initial_zoom = None;
let mut min_zoom = None;
let mut max_zoom = None;
let mut user_zoom = UserZoom::Zoom;
let mut orientation = Orientation::Auto;
// collapse the list of declarations into descriptor values
for declaration in rule.declarations.iter() {
match declaration.descriptor {
ViewportDescriptor::MinWidth(value) => min_width = Some(value),
ViewportDescriptor::MaxWidth(value) => max_width = Some(value),
ViewportDescriptor::MinHeight(value) => min_height = Some(value),
ViewportDescriptor::MaxHeight(value) => max_height = Some(value),
ViewportDescriptor::Zoom(value) => initial_zoom = value.to_f32(),
ViewportDescriptor::MinZoom(value) => min_zoom = value.to_f32(),
ViewportDescriptor::MaxZoom(value) => max_zoom = value.to_f32(),
ViewportDescriptor::UserZoom(value) => user_zoom = value,
ViewportDescriptor::Orientation(value) => orientation = value
}
}
// TODO: return `None` if all descriptors are either absent or initial value
macro_rules! choose {
($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())),
}
}
}
macro_rules! min {
($opta:expr, $optb:expr) => {
choose!(min, $opta, $optb)
}
}
macro_rules! max {
($opta:expr, $optb:expr) => {
choose!(max, $opta, $optb)
}
}
// 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()))
}
// DEVICE-ADAPT § 6.2.2 Constrain zoom value to the [min-zoom, max-zoom] range
if initial_zoom.is_some() {
initial_zoom = max!(min_zoom, min!(max_zoom, initial_zoom));
}
// DEVICE-ADAPT § 6.2.3 Resolve non-auto lengths to pixel lengths
//
// Note: DEVICE-ADAPT § 5. states that relative length values are
// resolved against initial values
let initial_viewport = Size2D(Au::from_f32_px(initial_viewport.width.get()),
Au::from_f32_px(initial_viewport.height.get()));
macro_rules! to_pixel_length {
($value:ident, $dimension:ident) => {
if let Some($value) = $value {
match $value {
LengthOrPercentageOrAuto::Length(ref value) => Some(match value {
&Length::Absolute(length) => length,
&Length::FontRelative(length) => {
let initial_font_size = longhands::font_size::get_initial_value();
length.to_computed_value(initial_font_size, initial_font_size)
}
&Length::ViewportPercentage(length) =>
length.to_computed_value(initial_viewport),
_ => unreachable!()
}),
LengthOrPercentageOrAuto::Percentage(value) => Some(initial_viewport.$dimension.scale_by(value)),
LengthOrPercentageOrAuto::Auto => None,
}
} else {
None
}
}
}
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 § 6.2.4 Resolve initial width and height from min/max descriptors
macro_rules! resolve {
($min:ident, $max:ident, $initial:expr) => {
if $min.is_some() || $max.is_some() {
let max = match $max {
Some(max) => cmp::min(max, $initial),
None => $initial
};
Some(match $min {
Some(min) => cmp::max(min, max),
None => max
})
} else {
None
};
}
}
let width = resolve!(min_width, max_width, initial_viewport.width);
let height = resolve!(min_height, max_height, initial_viewport.height);
// DEVICE-ADAPT § 6.2.5 Resolve width value
let width = if width.is_none() && height.is_none() {
Some(initial_viewport.width)
} else {
width
};
let width = width.unwrap_or_else(|| match initial_viewport.height {
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)
}
});
// DEVICE-ADAPT § 6.2.6 Resolve height value
let height = height.unwrap_or_else(|| match initial_viewport.width {
Au(0) => initial_viewport.height,
initial_width => {
let ratio = initial_viewport.height.to_f32_px() / initial_width.to_f32_px();
Au::from_f32_px(width.to_f32_px() * ratio)
}
});
Some(ViewportConstraints {
size: TypedSize2D(width.to_f32_px(), height.to_f32_px()),
// TODO: compute a zoom factor for 'auto' as suggested by DEVICE-ADAPT § 10.
initial_zoom: ScaleFactor::new(initial_zoom.unwrap_or(1.)),
min_zoom: min_zoom.map(ScaleFactor::new),
max_zoom: max_zoom.map(ScaleFactor::new),
user_zoom: user_zoom,
orientation: orientation
})
}
}

3
ports/cef/Cargo.lock generated
View file

@ -131,9 +131,11 @@ dependencies = [
"msg 0.0.1",
"net 0.0.1",
"net_traits 0.0.1",
"num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"profile_traits 0.0.1",
"script_traits 0.0.1",
"style 0.0.1",
"time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
@ -1127,6 +1129,7 @@ dependencies = [
"lazy_static 0.1.10 (git+https://github.com/Kimundi/lazy-static.rs)",
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
"plugins 0.0.1",
"rustc-serialize 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.1.0 (git+https://github.com/servo/rust-selectors)",

3
ports/gonk/Cargo.lock generated
View file

@ -114,9 +114,11 @@ dependencies = [
"msg 0.0.1",
"net 0.0.1",
"net_traits 0.0.1",
"num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"profile_traits 0.0.1",
"script_traits 0.0.1",
"style 0.0.1",
"time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
@ -1099,6 +1101,7 @@ dependencies = [
"lazy_static 0.1.10 (git+https://github.com/Kimundi/lazy-static.rs)",
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
"plugins 0.0.1",
"rustc-serialize 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.1.0 (git+https://github.com/servo/rust-selectors)",

View file

@ -311,6 +311,7 @@ resolution=800x600 == viewport_percentage_vmin_vmax.html viewport_percentage_vmi
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
resolution=600x800 == viewport_percentage_vw_vh.html viewport_percentage_vw_vh_b.html
experimental == viewport_rule.html viewport_rule_ref.html
== visibility_hidden.html visibility_hidden_ref.html
flaky_cpu == webgl-context/clearcolor.html webgl-context/clearcolor_ref.html

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<style>
@viewport {
height: auto;
width: 240px;
}
#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>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<style>
#container {
background: blue;
height: 180px;
width: 232px;
}
#box {
background: green;
height: 90px;
width: 120px;
}
</style>
</head>
<body>
<div id="container">
<div id="box">
</div>
</div>
</body>
</html>

View file

@ -16,6 +16,7 @@ extern crate util;
#[cfg(test)] mod stylesheets;
#[cfg(test)] mod media_queries;
#[cfg(test)] mod viewport;
#[cfg(test)] mod writing_modes {
use util::logical_geometry::WritingMode;

View file

@ -0,0 +1,275 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::Parser;
use geom::size::TypedSize2D;
use geom::scale_factor::ScaleFactor;
use style::media_queries::{Device, MediaType};
use style::parser::ParserContext;
use style::stylesheets::{Origin, Stylesheet, CSSRuleIteratorExt};
use style::values::specified::{Length, LengthOrPercentageOrAuto};
use style::viewport::*;
use url::Url;
macro_rules! stylesheet {
($css:expr, $origin:ident) => {
Stylesheet::from_str($css,
Url::parse("http://localhost").unwrap(),
Origin::$origin);
}
}
fn test_viewport_rule<F>(css: &str,
device: &Device,
callback: F)
where F: Fn(&Vec<ViewportDescriptorDeclaration>, &str)
{
::util::opts::set_experimental_enabled(true);
let stylesheet = stylesheet!(css, Author);
let mut rule_count = 0;
for rule in stylesheet.effective_rules(&device).viewport() {
rule_count += 1;
callback(&rule.declarations, css);
}
assert!(rule_count > 0);
}
macro_rules! assert_declarations_len {
($declarations:ident == 1) => {
assert!($declarations.len() == 1,
"expected 1 declaration; have {}: {:?})",
$declarations.len(), $declarations)
};
($declarations:ident == $len:expr) => {
assert!($declarations.len() == $len,
"expected {} declarations; have {}: {:?})",
$len, $declarations.len(), $declarations)
}
}
#[test]
fn empty_viewport_rule() {
let device = Device::new(MediaType::Screen, TypedSize2D(800., 600.));
test_viewport_rule("@viewport {}", &device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 0);
});
}
macro_rules! assert_declaration_eq {
($d:expr, $origin:ident, $expected:ident: $value:expr) => {{
assert_eq!($d.origin, Origin::$origin);
assert_eq!($d.descriptor, ViewportDescriptor::$expected($value));
assert!($d.important == false, "descriptor should not be !important");
}};
($d:expr, $origin:ident, $expected:ident: $value:expr, !important) => {{
assert_eq!($d.origin, Origin::$origin);
assert_eq!($d.descriptor, ViewportDescriptor::$expected($value));
assert!($d.important == true, "descriptor should be !important");
}};
}
#[test]
fn simple_viewport_rules() {
let device = Device::new(MediaType::Screen, TypedSize2D(800., 600.));
test_viewport_rule("@viewport { width: auto; height: auto;\
zoom: auto; min-zoom: 0; max-zoom: 200%;\
user-zoom: zoom; orientation: auto; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 9);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto);
assert_declaration_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto);
assert_declaration_eq!(&declarations[2], Author, MinHeight: LengthOrPercentageOrAuto::Auto);
assert_declaration_eq!(&declarations[3], Author, MaxHeight: LengthOrPercentageOrAuto::Auto);
assert_declaration_eq!(&declarations[4], Author, Zoom: Zoom::Auto);
assert_declaration_eq!(&declarations[5], Author, MinZoom: Zoom::Number(0.));
assert_declaration_eq!(&declarations[6], Author, MaxZoom: Zoom::Percentage(2.));
assert_declaration_eq!(&declarations[7], Author, UserZoom: UserZoom::Zoom);
assert_declaration_eq!(&declarations[8], Author, Orientation: Orientation::Auto);
});
test_viewport_rule("@viewport { min-width: 200px; max-width: auto;\
min-height: 200px; max-height: auto; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 4);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(200.)));
assert_declaration_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto);
assert_declaration_eq!(&declarations[2], Author, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.)));
assert_declaration_eq!(&declarations[3], Author, MaxHeight: LengthOrPercentageOrAuto::Auto);
});
}
#[test]
fn cascading_within_viewport_rule() {
let device = Device::new(MediaType::Screen, TypedSize2D(800., 600.));
// normal order of appearance
test_viewport_rule("@viewport { min-width: 200px; min-width: auto; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 1);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto);
});
// !important order of appearance
test_viewport_rule("@viewport { min-width: 200px !important; min-width: auto !important; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 1);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important);
});
// !important vs normal
test_viewport_rule("@viewport { min-width: auto !important; min-width: 200px; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 1);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important);
});
// normal longhands vs normal shorthand
test_viewport_rule("@viewport { min-width: 200px; max-width: 200px; width: auto; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 2);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto);
assert_declaration_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto);
});
// normal shorthand vs normal longhands
test_viewport_rule("@viewport { width: 200px; min-width: auto; max-width: auto; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 2);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto);
assert_declaration_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto);
});
// one !important longhand vs normal shorthand
test_viewport_rule("@viewport { min-width: auto !important; width: 200px; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 2);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important);
assert_declaration_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Length(Length::from_px(200.)));
});
// both !important longhands vs normal shorthand
test_viewport_rule("@viewport { min-width: auto !important; max-width: auto !important; width: 200px; }",
&device, |declarations, css| {
println!("{}", css);
assert_declarations_len!(declarations == 2);
assert_declaration_eq!(&declarations[0], Author, MinWidth: LengthOrPercentageOrAuto::Auto, !important);
assert_declaration_eq!(&declarations[1], Author, MaxWidth: LengthOrPercentageOrAuto::Auto, !important);
});
}
#[test]
fn multiple_stylesheets_cascading() {
let device = Device::new(MediaType::Screen, TypedSize2D(800., 600.));
let stylesheets = vec![
stylesheet!("@viewport { min-width: 100px; min-height: 100px; zoom: 1; }", UserAgent),
stylesheet!("@viewport { min-width: 200px; min-height: 200px; }", User),
stylesheet!("@viewport { min-width: 300px; }", Author)];
let declarations = stylesheets.iter()
.flat_map(|s| s.effective_rules(&device).viewport())
.cascade()
.declarations;
assert_declarations_len!(declarations == 3);
assert_declaration_eq!(&declarations[0], UserAgent, Zoom: Zoom::Number(1.));
assert_declaration_eq!(&declarations[1], User, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.)));
assert_declaration_eq!(&declarations[2], Author, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(300.)));
let stylesheets = vec![
stylesheet!("@viewport { min-width: 100px !important; }", UserAgent),
stylesheet!("@viewport { min-width: 200px !important; min-height: 200px !important; }", User),
stylesheet!("@viewport { min-width: 300px !important; min-height: 300px !important; zoom: 3 !important; }", Author)];
let declarations = stylesheets.iter()
.flat_map(|s| s.effective_rules(&device).viewport())
.cascade()
.declarations;
assert_declarations_len!(declarations == 3);
assert_declaration_eq!(&declarations[0], UserAgent, MinWidth: LengthOrPercentageOrAuto::Length(Length::from_px(100.)), !important);
assert_declaration_eq!(&declarations[1], User, MinHeight: LengthOrPercentageOrAuto::Length(Length::from_px(200.)), !important);
assert_declaration_eq!(&declarations[2], Author, Zoom: Zoom::Number(3.), !important);
}
#[test]
fn constrain_viewport() {
let url = Url::parse("http://localhost").unwrap();
let context = ParserContext::new(Origin::Author, &url);
macro_rules! from_css {
($css:expr) => {
&ViewportRule::parse(&mut Parser::new($css), &context).unwrap()
}
}
let initial_viewport = TypedSize2D(800., 600.);
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("")),
None);
let initial_viewport = TypedSize2D(800., 600.);
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
Some(ViewportConstraints {
size: initial_viewport,
initial_zoom: ScaleFactor::new(1.),
min_zoom: None,
max_zoom: None,
user_zoom: UserZoom::Zoom,
orientation: Orientation::Auto
}));
let initial_viewport = TypedSize2D(200., 150.);
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
Some(ViewportConstraints {
size: TypedSize2D(320., 240.),
initial_zoom: ScaleFactor::new(1.),
min_zoom: None,
max_zoom: None,
user_zoom: UserZoom::Zoom,
orientation: Orientation::Auto
}));
let initial_viewport = TypedSize2D(800., 600.);
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
Some(ViewportConstraints {
size: initial_viewport,
initial_zoom: ScaleFactor::new(1.),
min_zoom: None,
max_zoom: None,
user_zoom: UserZoom::Zoom,
orientation: Orientation::Auto
}));
let initial_viewport = TypedSize2D(800., 600.);
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 800px; height: 600px;\
zoom: 1;\
user-zoom: zoom;\
orientation: auto;")),
Some(ViewportConstraints {
size: initial_viewport,
initial_zoom: ScaleFactor::new(1.),
min_zoom: None,
max_zoom: None,
user_zoom: UserZoom::Zoom,
orientation: Orientation::Auto
}));
}