mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
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:
commit
ccf1e6b9a7
24 changed files with 1030 additions and 71 deletions
|
@ -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 = "*"
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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")]
|
||||
|
|
3
components/servo/Cargo.lock
generated
3
components/servo/Cargo.lock
generated
|
@ -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)",
|
||||
|
|
|
@ -38,3 +38,4 @@ url = "0.2.16"
|
|||
mod_path = "0.1"
|
||||
bitflags = "*"
|
||||
cssparser = "0.3.1"
|
||||
num = "0.1.24"
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
509
components/style/viewport.rs
Normal file
509
components/style/viewport.rs
Normal 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
3
ports/cef/Cargo.lock
generated
|
@ -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
3
ports/gonk/Cargo.lock
generated
|
@ -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)",
|
||||
|
|
|
@ -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
|
||||
|
|
29
tests/ref/viewport_rule.html
Normal file
29
tests/ref/viewport_rule.html
Normal 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>
|
24
tests/ref/viewport_rule_ref.html
Normal file
24
tests/ref/viewport_rule_ref.html
Normal 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>
|
|
@ -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;
|
||||
|
|
275
tests/unit/style/viewport.rs
Normal file
275
tests/unit/style/viewport.rs
Normal 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
|
||||
}));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue