mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Implement parsing of an @viewport rule
This commit is contained in:
parent
c303e9dcd5
commit
3b14c07051
7 changed files with 557 additions and 16 deletions
|
@ -50,6 +50,7 @@ pub mod media_queries;
|
||||||
pub mod font_face;
|
pub mod font_face;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
pub mod viewport;
|
||||||
|
|
||||||
macro_rules! reexport_computed_values {
|
macro_rules! reexport_computed_values {
|
||||||
( $( $name: ident )+ ) => {
|
( $( $name: ident )+ ) => {
|
||||||
|
@ -63,4 +64,3 @@ macro_rules! reexport_computed_values {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
longhand_properties_idents!(reexport_computed_values);
|
longhand_properties_idents!(reexport_computed_values);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ use log;
|
||||||
use stylesheets::Origin;
|
use stylesheets::Origin;
|
||||||
|
|
||||||
pub struct ParserContext<'a> {
|
pub struct ParserContext<'a> {
|
||||||
|
pub stylesheet_origin: Origin,
|
||||||
pub base_url: &'a Url,
|
pub base_url: &'a Url,
|
||||||
pub selector_context: SelectorParserContext,
|
pub selector_context: SelectorParserContext,
|
||||||
}
|
}
|
||||||
|
@ -20,6 +21,7 @@ impl<'a> ParserContext<'a> {
|
||||||
let mut selector_context = SelectorParserContext::new();
|
let mut selector_context = SelectorParserContext::new();
|
||||||
selector_context.in_user_agent_stylesheet = stylesheet_origin == Origin::UserAgent;
|
selector_context.in_user_agent_stylesheet = stylesheet_origin == Origin::UserAgent;
|
||||||
ParserContext {
|
ParserContext {
|
||||||
|
stylesheet_origin: stylesheet_origin,
|
||||||
base_url: base_url,
|
base_url: base_url,
|
||||||
selector_context: selector_context,
|
selector_context: selector_context,
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ use properties::{PropertyDeclarationBlock, parse_property_declaration_list};
|
||||||
use media_queries::{Device, MediaQueryList, parse_media_query_list};
|
use media_queries::{Device, MediaQueryList, parse_media_query_list};
|
||||||
use font_face::{FontFaceRule, parse_font_face_block};
|
use font_face::{FontFaceRule, parse_font_face_block};
|
||||||
use util::smallvec::SmallVec2;
|
use util::smallvec::SmallVec2;
|
||||||
|
use viewport::ViewportRule;
|
||||||
|
|
||||||
|
|
||||||
/// Each style rule has an origin, which determines where it enters the cascade.
|
/// Each style rule has an origin, which determines where it enters the cascade.
|
||||||
|
@ -53,6 +54,7 @@ pub enum CSSRule {
|
||||||
Style(StyleRule),
|
Style(StyleRule),
|
||||||
Media(MediaRule),
|
Media(MediaRule),
|
||||||
FontFace(FontFaceRule),
|
FontFace(FontFaceRule),
|
||||||
|
Viewport(ViewportRule),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -216,6 +218,7 @@ pub mod rule_filter {
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use super::{CSSRule, MediaRule, StyleRule};
|
use super::{CSSRule, MediaRule, StyleRule};
|
||||||
use super::super::font_face::FontFaceRule;
|
use super::super::font_face::FontFaceRule;
|
||||||
|
use super::super::viewport::ViewportRule;
|
||||||
|
|
||||||
macro_rules! rule_filter {
|
macro_rules! rule_filter {
|
||||||
($variant:ident -> $value:ty) => {
|
($variant:ident -> $value:ty) => {
|
||||||
|
@ -259,6 +262,7 @@ pub mod rule_filter {
|
||||||
rule_filter!(FontFace -> FontFaceRule);
|
rule_filter!(FontFace -> FontFaceRule);
|
||||||
rule_filter!(Media -> MediaRule);
|
rule_filter!(Media -> MediaRule);
|
||||||
rule_filter!(Style -> StyleRule);
|
rule_filter!(Style -> StyleRule);
|
||||||
|
rule_filter!(Viewport -> ViewportRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extension methods for `CSSRule` iterators.
|
/// Extension methods for `CSSRule` iterators.
|
||||||
|
@ -271,6 +275,9 @@ pub trait CSSRuleIteratorExt<'a>: Iterator<Item=&'a CSSRule> {
|
||||||
|
|
||||||
/// Yield only style rules.
|
/// Yield only style rules.
|
||||||
fn style(self) -> rule_filter::Style<'a, Self>;
|
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> {
|
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> {
|
fn style(self) -> rule_filter::Style<'a, I> {
|
||||||
rule_filter::Style::new(self)
|
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> {
|
fn parse_nested_rules(context: &ParserContext, input: &mut Parser) -> Vec<CSSRule> {
|
||||||
|
@ -324,6 +336,7 @@ enum State {
|
||||||
enum AtRulePrelude {
|
enum AtRulePrelude {
|
||||||
FontFace,
|
FontFace,
|
||||||
Media(MediaQueryList),
|
Media(MediaQueryList),
|
||||||
|
Viewport,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -414,6 +427,9 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
|
||||||
},
|
},
|
||||||
"font-face" => {
|
"font-face" => {
|
||||||
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace))
|
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace))
|
||||||
|
},
|
||||||
|
"viewport" => {
|
||||||
|
Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
|
||||||
}
|
}
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
|
@ -430,6 +446,9 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
|
||||||
rules: parse_nested_rules(self.context, input),
|
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 util::geometry::Au;
|
||||||
use super::CSSFloat;
|
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)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct CSSColor {
|
pub struct CSSColor {
|
||||||
pub parsed: cssparser::Color,
|
pub parsed: cssparser::Color,
|
||||||
|
@ -397,33 +413,32 @@ pub mod specified {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LengthOrPercentageOrAuto {
|
impl LengthOrPercentageOrAuto {
|
||||||
fn parse_internal(input: &mut Parser, negative_ok: bool)
|
fn parse_internal(input: &mut Parser, context: &AllowedNumericType)
|
||||||
-> Result<LengthOrPercentageOrAuto, ()> {
|
-> Result<LengthOrPercentageOrAuto, ()>
|
||||||
|
{
|
||||||
match try!(input.next()) {
|
match try!(input.next()) {
|
||||||
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
|
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) => {
|
||||||
Length::parse_dimension(value.value, unit)
|
Length::parse_dimension(value.value, unit)
|
||||||
.map(LengthOrPercentageOrAuto::Length)
|
.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::Percentage(ref value) if context.is_ok(value.unit_value) =>
|
||||||
|
Ok(LengthOrPercentageOrAuto::Percentage(value.unit_value)),
|
||||||
|
Token::Number(ref value) if context.is_ok(value.value) =>
|
||||||
|
Ok(LengthOrPercentageOrAuto::Length(Length::Absolute(Au(0)))),
|
||||||
|
Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") =>
|
||||||
|
Ok(LengthOrPercentageOrAuto::Auto),
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
|
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
|
||||||
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
|
LengthOrPercentageOrAuto::parse_internal(input, &AllowedNumericType::All)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
|
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
|
||||||
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
|
LengthOrPercentageOrAuto::parse_internal(input, &AllowedNumericType::NonNegative)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
302
components/style/viewport.rs
Normal file
302
components/style/viewport.rs
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
/* 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 parser::{ParserContext, log_css_error};
|
||||||
|
use stylesheets::Origin;
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ extern crate util;
|
||||||
|
|
||||||
#[cfg(test)] mod stylesheets;
|
#[cfg(test)] mod stylesheets;
|
||||||
#[cfg(test)] mod media_queries;
|
#[cfg(test)] mod media_queries;
|
||||||
|
#[cfg(test)] mod viewport;
|
||||||
|
|
||||||
#[cfg(test)] mod writing_modes {
|
#[cfg(test)] mod writing_modes {
|
||||||
use util::logical_geometry::WritingMode;
|
use util::logical_geometry::WritingMode;
|
||||||
|
|
202
tests/unit/style/viewport.rs
Normal file
202
tests/unit/style/viewport.rs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue