Make parsing of keyframe declaration blocks spec-compliant.

Exclude animation properties.
This commit is contained in:
Simon Sapin 2016-08-17 20:49:06 +02:00
parent 901aaeaf68
commit ab846ab196
6 changed files with 115 additions and 21 deletions

View file

@ -2,10 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::{AtRuleParser, Delimiter, Parser, QualifiedRuleParser, RuleListParser}; use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
use cssparser::{DeclarationListParser, DeclarationParser};
use parser::{ParserContext, log_css_error}; use parser::{ParserContext, log_css_error};
use properties::PropertyDeclaration;
use properties::PropertyDeclarationParseResult;
use properties::animated_properties::TransitionProperty; use properties::animated_properties::TransitionProperty;
use properties::{PropertyDeclaration, parse_property_declaration_list};
use std::sync::Arc; use std::sync::Arc;
/// A number from 1 to 100, indicating the percentage of the animation where /// A number from 1 to 100, indicating the percentage of the animation where
@ -238,12 +240,49 @@ impl<'a> QualifiedRuleParser for KeyframeListParser<'a> {
fn parse_block(&self, prelude: Self::Prelude, input: &mut Parser) fn parse_block(&self, prelude: Self::Prelude, input: &mut Parser)
-> Result<Self::QualifiedRule, ()> { -> Result<Self::QualifiedRule, ()> {
let mut declarations = Vec::new();
let parser = KeyframeDeclarationParser {
context: self.context,
};
let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() {
match declaration {
Ok(d) => declarations.extend(d),
Err(range) => {
let pos = range.start;
let message = format!("Unsupported keyframe property declaration: '{}'",
iter.input.slice(range));
log_css_error(iter.input, pos, &*message, self.context);
}
}
// `parse_important` is not called here, `!important` is not allowed in keyframe blocks.
}
Ok(Keyframe { Ok(Keyframe {
selector: prelude, selector: prelude,
// FIXME: needs parsing different from parse_property_declaration_list: declarations: Arc::new(declarations),
// https://drafts.csswg.org/css-animations/#keyframes
// Paragraph "The <declaration-list> inside of <keyframe-block> ..."
declarations: parse_property_declaration_list(self.context, input).normal,
}) })
} }
} }
struct KeyframeDeclarationParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
}
/// Default methods reject all at rules.
impl<'a, 'b> AtRuleParser for KeyframeDeclarationParser<'a, 'b> {
type Prelude = ();
type AtRule = Vec<PropertyDeclaration>;
}
impl<'a, 'b> DeclarationParser for KeyframeDeclarationParser<'a, 'b> {
type Declaration = Vec<PropertyDeclaration>;
fn parse_value(&self, name: &str, input: &mut Parser) -> Result<Vec<PropertyDeclaration>, ()> {
let mut results = Vec::new();
match PropertyDeclaration::parse(name, self.context, input, &mut results, true) {
PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => {}
_ => return Err(())
}
Ok(results)
}
}

View file

@ -65,7 +65,8 @@ class Keyword(object):
class Longhand(object): class Longhand(object):
def __init__(self, style_struct, name, animatable=None, derived_from=None, keyword=None, def __init__(self, style_struct, name, animatable=None, derived_from=None, keyword=None,
predefined_type=None, custom_cascade=False, experimental=False, internal=False, predefined_type=None, custom_cascade=False, experimental=False, internal=False,
need_clone=False, need_index=False, gecko_ffi_name=None, depend_on_viewport_size=False): need_clone=False, need_index=False, gecko_ffi_name=None, depend_on_viewport_size=False,
allowed_in_keyframe_block=True):
self.name = name self.name = name
self.keyword = keyword self.keyword = keyword
self.predefined_type = predefined_type self.predefined_type = predefined_type
@ -80,6 +81,13 @@ class Longhand(object):
self.depend_on_viewport_size = depend_on_viewport_size self.depend_on_viewport_size = depend_on_viewport_size
self.derived_from = (derived_from or "").split() self.derived_from = (derived_from or "").split()
# https://drafts.csswg.org/css-animations/#keyframes
# > The <declaration-list> inside of <keyframe-block> accepts any CSS property
# > except those defined in this specification,
# > but does accept the `animation-play-state` property and interprets it specially.
self.allowed_in_keyframe_block = allowed_in_keyframe_block \
and allowed_in_keyframe_block != "False"
# This is done like this since just a plain bool argument seemed like # This is done like this since just a plain bool argument seemed like
# really random. # really random.
if animatable is None: if animatable is None:
@ -98,7 +106,8 @@ class Longhand(object):
class Shorthand(object): class Shorthand(object):
def __init__(self, name, sub_properties, experimental=False, internal=False): def __init__(self, name, sub_properties, experimental=False, internal=False,
allowed_in_keyframe_block=True):
self.name = name self.name = name
self.ident = to_rust_ident(name) self.ident = to_rust_ident(name)
self.camel_case = to_camel_case(self.ident) self.camel_case = to_camel_case(self.ident)
@ -107,6 +116,13 @@ class Shorthand(object):
self.sub_properties = sub_properties self.sub_properties = sub_properties
self.internal = internal self.internal = internal
# https://drafts.csswg.org/css-animations/#keyframes
# > The <declaration-list> inside of <keyframe-block> accepts any CSS property
# > except those defined in this specification,
# > but does accept the `animation-play-state` property and interprets it specially.
self.allowed_in_keyframe_block = allowed_in_keyframe_block \
and allowed_in_keyframe_block != "False"
class Method(object): class Method(object):
def __init__(self, name, return_type=None, arg_types=None, is_mut=False): def __init__(self, name, return_type=None, arg_types=None, is_mut=False):

View file

@ -636,7 +636,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
<%helpers:longhand name="animation-name" <%helpers:longhand name="animation-name"
need_index="True" need_index="True"
animatable="False"> animatable="False",
allowed_in_keyframe_block="False">
use values::computed::ComputedValueAsSpecified; use values::computed::ComputedValueAsSpecified;
use values::NoViewportPercentage; use values::NoViewportPercentage;
@ -699,7 +700,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
<%helpers:longhand name="animation-duration" <%helpers:longhand name="animation-duration"
need_index="True" need_index="True"
animatable="False"> animatable="False",
allowed_in_keyframe_block="False">
pub use super::transition_duration::computed_value; pub use super::transition_duration::computed_value;
pub use super::transition_duration::{get_initial_value, get_initial_single_value}; pub use super::transition_duration::{get_initial_value, get_initial_single_value};
pub use super::transition_duration::{parse, parse_one}; pub use super::transition_duration::{parse, parse_one};
@ -709,7 +711,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
<%helpers:longhand name="animation-timing-function" <%helpers:longhand name="animation-timing-function"
need_index="True" need_index="True"
animatable="False"> animatable="False",
allowed_in_keyframe_block="False">
pub use super::transition_timing_function::computed_value; pub use super::transition_timing_function::computed_value;
pub use super::transition_timing_function::{get_initial_value, get_initial_single_value}; pub use super::transition_timing_function::{get_initial_value, get_initial_single_value};
pub use super::transition_timing_function::{parse, parse_one}; pub use super::transition_timing_function::{parse, parse_one};
@ -719,7 +722,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
<%helpers:longhand name="animation-iteration-count" <%helpers:longhand name="animation-iteration-count"
need_index="True" need_index="True"
animatable="False"> animatable="False",
allowed_in_keyframe_block="False">
use values::computed::ComputedValueAsSpecified; use values::computed::ComputedValueAsSpecified;
use values::NoViewportPercentage; use values::NoViewportPercentage;
@ -804,22 +808,28 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
${helpers.keyword_list("animation-direction", ${helpers.keyword_list("animation-direction",
"normal reverse alternate alternate-reverse", "normal reverse alternate alternate-reverse",
need_index=True, need_index=True,
animatable=False)} animatable=False,
allowed_in_keyframe_block=False)}
// animation-play-state is the exception to the rule for allowed_in_keyframe_block:
// https://drafts.csswg.org/css-animations/#keyframes
${helpers.keyword_list("animation-play-state", ${helpers.keyword_list("animation-play-state",
"running paused", "running paused",
need_clone=True, need_clone=True,
need_index=True, need_index=True,
animatable=False)} animatable=False,
allowed_in_keyframe_block=True)}
${helpers.keyword_list("animation-fill-mode", ${helpers.keyword_list("animation-fill-mode",
"none forwards backwards both", "none forwards backwards both",
need_index=True, need_index=True,
animatable=False)} animatable=False,
allowed_in_keyframe_block=False)}
<%helpers:longhand name="animation-delay" <%helpers:longhand name="animation-delay"
need_index="True" need_index="True"
animatable="False"> animatable="False",
allowed_in_keyframe_block="False">
pub use super::transition_duration::computed_value; pub use super::transition_duration::computed_value;
pub use super::transition_duration::{get_initial_value, get_initial_single_value}; pub use super::transition_duration::{get_initial_value, get_initial_single_value};
pub use super::transition_duration::{parse, parse_one}; pub use super::transition_duration::{parse, parse_one};

View file

@ -517,7 +517,7 @@ pub fn parse_one_declaration(name: &str, input: &str, base_url: &Url, error_repo
-> Result<Vec<PropertyDeclaration>, ()> { -> Result<Vec<PropertyDeclaration>, ()> {
let context = ParserContext::new_with_extra_data(Origin::Author, base_url, error_reporter, extra_data); let context = ParserContext::new_with_extra_data(Origin::Author, base_url, error_reporter, extra_data);
let mut results = vec![]; let mut results = vec![];
match PropertyDeclaration::parse(name, &context, &mut Parser::new(input), &mut results) { match PropertyDeclaration::parse(name, &context, &mut Parser::new(input), &mut results, false) {
PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => Ok(results), PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => Ok(results),
_ => Err(()) _ => Err(())
} }
@ -542,7 +542,7 @@ impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
-> Result<(Vec<PropertyDeclaration>, Importance), ()> { -> Result<(Vec<PropertyDeclaration>, Importance), ()> {
let mut results = vec![]; let mut results = vec![];
try!(input.parse_until_before(Delimiter::Bang, |input| { try!(input.parse_until_before(Delimiter::Bang, |input| {
match PropertyDeclaration::parse(name, self.context, input, &mut results) { match PropertyDeclaration::parse(name, self.context, input, &mut results, false) {
PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => Ok(()), PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => Ok(()),
_ => Err(()) _ => Err(())
} }
@ -832,6 +832,7 @@ pub enum PropertyDeclarationParseResult {
UnknownProperty, UnknownProperty,
ExperimentalProperty, ExperimentalProperty,
InvalidValue, InvalidValue,
AnimationPropertyInKeyframeBlock,
ValidOrIgnoredDeclaration, ValidOrIgnoredDeclaration,
} }
@ -984,8 +985,16 @@ impl PropertyDeclaration {
} }
} }
/// The `in_keyframe_block` parameter controls this:
///
/// https://drafts.csswg.org/css-animations/#keyframes
/// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
/// > except those defined in this specification,
/// > but does accept the `animation-play-state` property and interprets it specially.
pub fn parse(name: &str, context: &ParserContext, input: &mut Parser, pub fn parse(name: &str, context: &ParserContext, input: &mut Parser,
result_list: &mut Vec<PropertyDeclaration>) -> PropertyDeclarationParseResult { result_list: &mut Vec<PropertyDeclaration>,
in_keyframe_block: bool)
-> PropertyDeclarationParseResult {
if let Ok(name) = ::custom_properties::parse_name(name) { if let Ok(name) = ::custom_properties::parse_name(name) {
let value = match input.try(CSSWideKeyword::parse) { let value = match input.try(CSSWideKeyword::parse) {
Ok(CSSWideKeyword::UnsetKeyword) | // Custom properties are alawys inherited Ok(CSSWideKeyword::UnsetKeyword) | // Custom properties are alawys inherited
@ -1003,6 +1012,11 @@ impl PropertyDeclaration {
% for property in data.longhands: % for property in data.longhands:
% if not property.derived_from: % if not property.derived_from:
"${property.name}" => { "${property.name}" => {
% if not property.allowed_in_keyframe_block:
if in_keyframe_block {
return PropertyDeclarationParseResult::AnimationPropertyInKeyframeBlock
}
% endif
% if property.internal: % if property.internal:
if context.stylesheet_origin != Origin::UserAgent { if context.stylesheet_origin != Origin::UserAgent {
return PropertyDeclarationParseResult::UnknownProperty return PropertyDeclarationParseResult::UnknownProperty
@ -1028,6 +1042,11 @@ impl PropertyDeclaration {
% endfor % endfor
% for shorthand in data.shorthands: % for shorthand in data.shorthands:
"${shorthand.name}" => { "${shorthand.name}" => {
% if not shorthand.allowed_in_keyframe_block:
if in_keyframe_block {
return PropertyDeclarationParseResult::AnimationPropertyInKeyframeBlock
}
% endif
% if shorthand.internal: % if shorthand.internal:
if context.stylesheet_origin != Origin::UserAgent { if context.stylesheet_origin != Origin::UserAgent {
return PropertyDeclarationParseResult::UnknownProperty return PropertyDeclarationParseResult::UnknownProperty

View file

@ -152,7 +152,8 @@ macro_rules! try_parse_one {
sub_properties="animation-name animation-duration sub_properties="animation-name animation-duration
animation-timing-function animation-delay animation-timing-function animation-delay
animation-iteration-count animation-direction animation-iteration-count animation-direction
animation-fill-mode animation-play-state"> animation-fill-mode animation-play-state"
allowed_in_keyframe_block="False">
use properties::longhands::{animation_name, animation_duration, animation_timing_function}; use properties::longhands::{animation_name, animation_duration, animation_timing_function};
use properties::longhands::{animation_delay, animation_iteration_count, animation_direction}; use properties::longhands::{animation_delay, animation_iteration_count, animation_direction};
use properties::longhands::{animation_fill_mode, animation_play_state}; use properties::longhands::{animation_fill_mode, animation_play_state};

View file

@ -13,6 +13,7 @@ use style::error_reporting::ParseErrorReporter;
use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage}; use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage};
use style::parser::ParserContextExtraData; use style::parser::ParserContextExtraData;
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands};
use style::properties::longhands::animation_play_state;
use style::stylesheets::{Stylesheet, CSSRule, StyleRule, KeyframesRule, Origin}; use style::stylesheets::{Stylesheet, CSSRule, StyleRule, KeyframesRule, Origin};
use style::values::specified::{LengthOrPercentageOrAuto, Percentage}; use style::values::specified::{LengthOrPercentageOrAuto, Percentage};
use url::Url; use url::Url;
@ -27,7 +28,12 @@ fn test_parse_stylesheet() {
#d1 > .ok { background: blue; } #d1 > .ok { background: blue; }
@keyframes foo { @keyframes foo {
from { width: 0% } from { width: 0% }
to { width: 100%} to {
width: 100%;
width: 50% !important; /* !important not allowed here */
animation-name: 'foo'; /* animation properties not allowed here */
animation-play-state: running; /* … except animation-play-state */
}
}"; }";
let url = Url::parse("about::test").unwrap(); let url = Url::parse("about::test").unwrap();
let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent, let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent,
@ -187,6 +193,9 @@ fn test_parse_stylesheet() {
declarations: Arc::new(vec![ declarations: Arc::new(vec![
PropertyDeclaration::Width(DeclaredValue::Value( PropertyDeclaration::Width(DeclaredValue::Value(
LengthOrPercentageOrAuto::Percentage(Percentage(1.)))), LengthOrPercentageOrAuto::Percentage(Percentage(1.)))),
PropertyDeclaration::AnimationPlayState(DeclaredValue::Value(
animation_play_state::SpecifiedValue(
vec![animation_play_state::SingleSpecifiedValue::running]))),
]), ]),
}, },
] ]