Auto merge of #16954 - servo:arrayvec, r=emilio

Avoid returning / passing around a huge ParsedDeclaration type

This enum type used to contain the result of parsing one CSS source declaration (`name: value;`) and expanding shorthands. Enum types are as big as the biggest of their variant (plus discriminant), which was quite big because some shorthands expand to many longhand properties. This type was returned through many functions and methods, wrapped and rewrapped in `Result` with different error types. This presumably caused significant `memmove` traffic.

Instead, we now allocate an `ArrayVec` on the stack and pass `&mut` references to it for various functions to push into it. This type is also very big, but we never move it.

We still use an intermediate data structure because we sometimes decide after shorthand expansion that a declaration is invalid after all and that we’re gonna drop it. Only later do we push to a `PropertyDeclarationBlock`, with an entire `ArrayVec` or nothing.

In future work we can try to avoid a large stack-allocated array, and instead writing directly to the heap allocation of the `Vec` inside `PropertyDeclarationBlock`. However this is tricky: we need to preserve this "all or nothing" aspect of parsing one source declaration, and at the same time we want to make it as little error-prone as possible for the various call sites. `PropertyDeclarationBlock` curently does property deduplication incrementally: as each `PropertyDeclaration` is pushed, we check if an existing declaration of the same property exists and if so overwrite it. To get rid of the stack allocated array we’d need to somehow deduplicate separately after pushing multiple `PropertyDeclaration`.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16954)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-05-19 18:37:14 -05:00 committed by GitHub
commit 60682cf81f
26 changed files with 488 additions and 386 deletions

1
Cargo.lock generated
View file

@ -2824,6 +2824,7 @@ name = "style"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)",
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bindgen 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)", "bindgen 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -19,7 +19,7 @@ use std::ascii::AsciiExt;
use style::attr::AttrValue; use style::attr::AttrValue;
use style::parser::PARSING_MODE_DEFAULT; use style::parser::PARSING_MODE_DEFAULT;
use style::properties::{Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId}; use style::properties::{Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId};
use style::properties::{parse_one_declaration, parse_style_attribute}; use style::properties::{parse_one_declaration_into, parse_style_attribute, SourcePropertyDeclaration};
use style::selector_parser::PseudoElement; use style::selector_parser::PseudoElement;
use style::shared_lock::Locked; use style::shared_lock::Locked;
use style::stylearc::Arc; use style::stylearc::Arc;
@ -239,12 +239,12 @@ impl CSSStyleDeclaration {
self.owner.mutate_associated_block(|ref mut pdb, mut changed| { self.owner.mutate_associated_block(|ref mut pdb, mut changed| {
if value.is_empty() { if value.is_empty() {
// Step 4 // Step 3
*changed = pdb.remove_property(&id); *changed = pdb.remove_property(&id);
return Ok(()); return Ok(());
} }
// Step 5 // Step 4
let importance = match &*priority { let importance = match &*priority {
"" => Importance::Normal, "" => Importance::Normal,
p if p.eq_ignore_ascii_case("important") => Importance::Important, p if p.eq_ignore_ascii_case("important") => Importance::Important,
@ -254,27 +254,26 @@ impl CSSStyleDeclaration {
} }
}; };
// Step 6 // Step 5
let window = self.owner.window(); let window = self.owner.window();
let quirks_mode = window.Document().quirks_mode(); let quirks_mode = window.Document().quirks_mode();
let result = let mut declarations = SourcePropertyDeclaration::new();
parse_one_declaration(id, &value, &self.owner.base_url(), let result = parse_one_declaration_into(
window.css_error_reporter(), &mut declarations, id, &value, &self.owner.base_url(),
PARSING_MODE_DEFAULT, window.css_error_reporter(), PARSING_MODE_DEFAULT, quirks_mode);
quirks_mode);
// Step 7 // Step 6
let parsed = match result { match result {
Ok(parsed) => parsed, Ok(()) => {},
Err(_) => { Err(_) => {
*changed = false; *changed = false;
return Ok(()); return Ok(());
} }
}; }
// Step 7
// Step 8 // Step 8
// Step 9 *changed = pdb.extend_reset(declarations.drain(), importance);
*changed = parsed.expand_set_into(pdb, importance);
Ok(()) Ok(())
}) })

View file

@ -21,12 +21,17 @@ use_bindgen = ["bindgen", "regex", "toml"]
servo = ["serde", "serde_derive", "heapsize", "heapsize_derive", servo = ["serde", "serde_derive", "heapsize", "heapsize_derive",
"style_traits/servo", "servo_atoms", "servo_config", "html5ever", "style_traits/servo", "servo_atoms", "servo_config", "html5ever",
"cssparser/heapsize", "cssparser/serde", "encoding", "smallvec/heapsizeof", "cssparser/heapsize", "cssparser/serde", "encoding", "smallvec/heapsizeof",
# FIXME: Uncomment when https://github.com/servo/servo/pull/16953 has landed:
#"arrayvec/use_union"
"rayon/unstable", "servo_url"] "rayon/unstable", "servo_url"]
testing = [] testing = []
gecko_debug = ["nsstring_vendor/gecko_debug"] gecko_debug = ["nsstring_vendor/gecko_debug"]
[dependencies] [dependencies]
app_units = "0.4" app_units = "0.4"
arrayvec = "0.3.20"
atomic_refcell = "0.1" atomic_refcell = "0.1"
bitflags = "0.7" bitflags = "0.7"
bit-vec = "0.4.3" bit-vec = "0.4.3"

View file

@ -11,7 +11,7 @@ use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule};
use error_reporting::NullReporter; use error_reporting::NullReporter;
use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error}; use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error};
use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId}; use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId};
use properties::{PropertyDeclarationId, LonghandId, ParsedDeclaration}; use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration};
use properties::LonghandIdSet; use properties::LonghandIdSet;
use properties::animated_properties::TransitionProperty; use properties::animated_properties::TransitionProperty;
use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction; use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
@ -142,9 +142,11 @@ impl Keyframe {
parent_stylesheet.quirks_mode); parent_stylesheet.quirks_mode);
let mut input = Parser::new(css); let mut input = Parser::new(css);
let mut declarations = SourcePropertyDeclaration::new();
let mut rule_parser = KeyframeListParser { let mut rule_parser = KeyframeListParser {
context: &context, context: &context,
shared_lock: &parent_stylesheet.shared_lock, shared_lock: &parent_stylesheet.shared_lock,
declarations: &mut declarations,
}; };
parse_one_rule(&mut input, &mut rule_parser) parse_one_rule(&mut input, &mut rule_parser)
} }
@ -345,14 +347,17 @@ impl KeyframesAnimation {
struct KeyframeListParser<'a> { struct KeyframeListParser<'a> {
context: &'a ParserContext<'a>, context: &'a ParserContext<'a>,
shared_lock: &'a SharedRwLock, shared_lock: &'a SharedRwLock,
declarations: &'a mut SourcePropertyDeclaration,
} }
/// Parses a keyframe list from CSS input. /// Parses a keyframe list from CSS input.
pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser, shared_lock: &SharedRwLock) pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser, shared_lock: &SharedRwLock)
-> Vec<Arc<Locked<Keyframe>>> { -> Vec<Arc<Locked<Keyframe>>> {
let mut declarations = SourcePropertyDeclaration::new();
RuleListParser::new_for_nested_rule(input, KeyframeListParser { RuleListParser::new_for_nested_rule(input, KeyframeListParser {
context: context, context: context,
shared_lock: shared_lock, shared_lock: shared_lock,
declarations: &mut declarations,
}).filter_map(Result::ok).collect() }).filter_map(Result::ok).collect()
} }
@ -383,13 +388,17 @@ impl<'a> QualifiedRuleParser for KeyframeListParser<'a> {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframe)); let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframe));
let parser = KeyframeDeclarationParser { let parser = KeyframeDeclarationParser {
context: &context, context: &context,
declarations: self.declarations,
}; };
let mut iter = DeclarationListParser::new(input, parser); let mut iter = DeclarationListParser::new(input, parser);
let mut block = PropertyDeclarationBlock::new(); let mut block = PropertyDeclarationBlock::new();
while let Some(declaration) = iter.next() { while let Some(declaration) = iter.next() {
match declaration { match declaration {
Ok(parsed) => parsed.expand_push_into(&mut block, Importance::Normal), Ok(()) => {
block.extend(iter.parser.declarations.drain(), Importance::Normal);
}
Err(range) => { Err(range) => {
iter.parser.declarations.clear();
let pos = range.start; let pos = range.start;
let message = format!("Unsupported keyframe property declaration: '{}'", let message = format!("Unsupported keyframe property declaration: '{}'",
iter.input.slice(range)); iter.input.slice(range));
@ -407,27 +416,24 @@ impl<'a> QualifiedRuleParser for KeyframeListParser<'a> {
struct KeyframeDeclarationParser<'a, 'b: 'a> { struct KeyframeDeclarationParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>, context: &'a ParserContext<'b>,
declarations: &'a mut SourcePropertyDeclaration,
} }
/// Default methods reject all at rules. /// Default methods reject all at rules.
impl<'a, 'b> AtRuleParser for KeyframeDeclarationParser<'a, 'b> { impl<'a, 'b> AtRuleParser for KeyframeDeclarationParser<'a, 'b> {
type Prelude = (); type Prelude = ();
type AtRule = ParsedDeclaration; type AtRule = ();
} }
impl<'a, 'b> DeclarationParser for KeyframeDeclarationParser<'a, 'b> { impl<'a, 'b> DeclarationParser for KeyframeDeclarationParser<'a, 'b> {
type Declaration = ParsedDeclaration; type Declaration = ();
fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<ParsedDeclaration, ()> { fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> {
let id = try!(PropertyId::parse(name.into())); let id = try!(PropertyId::parse(name.into()));
match ParsedDeclaration::parse(id, self.context, input) { match PropertyDeclaration::parse_into(self.declarations, id, self.context, input) {
Ok(parsed) => { Ok(()) => {
// In case there is still unparsed text in the declaration, we should roll back. // In case there is still unparsed text in the declaration, we should roll back.
if !input.is_exhausted() { input.expect_exhausted()
Err(())
} else {
Ok(parsed)
}
} }
Err(_) => Err(()) Err(_) => Err(())
} }

View file

@ -38,6 +38,7 @@
#![recursion_limit = "500"] // For define_css_keyword_enum! in -moz-appearance #![recursion_limit = "500"] // For define_css_keyword_enum! in -moz-appearance
extern crate app_units; extern crate app_units;
extern crate arrayvec;
extern crate atomic_refcell; extern crate atomic_refcell;
extern crate bit_vec; extern crate bit_vec;
#[macro_use] #[macro_use]

View file

@ -202,13 +202,61 @@ impl PropertyDeclarationBlock {
} }
/// Adds or overrides the declaration for a given property in this block, /// Adds or overrides the declaration for a given property in this block,
/// except if an existing declaration for the same property is more important. /// **except** if an existing declaration for the same property is more important.
pub fn extend(&mut self, drain: SourcePropertyDeclarationDrain, importance: Importance) {
self.extend_common(drain, importance, false);
}
/// Adds or overrides the declaration for a given property in this block,
/// **even** if an existing declaration for the same property is more important.
///
/// Return whether anything changed.
pub fn extend_reset(&mut self, drain: SourcePropertyDeclarationDrain,
importance: Importance) -> bool {
self.extend_common(drain, importance, true)
}
fn extend_common(&mut self, mut drain: SourcePropertyDeclarationDrain,
importance: Importance, overwrite_more_important: bool) -> bool {
let all_shorthand_len = match drain.all_shorthand {
AllShorthand::NotSet => 0,
AllShorthand::CSSWideKeyword(_) |
AllShorthand::WithVariables(_) => ShorthandId::All.longhands().len()
};
let push_calls_count = drain.declarations.len() + all_shorthand_len;
// With deduplication the actual length increase may be less than this.
self.declarations.reserve(push_calls_count);
let mut changed = false;
for decl in &mut drain.declarations {
changed |= self.push_common(decl, importance, overwrite_more_important);
}
match drain.all_shorthand {
AllShorthand::NotSet => {}
AllShorthand::CSSWideKeyword(keyword) => {
for &id in ShorthandId::All.longhands() {
let decl = PropertyDeclaration::CSSWideKeyword(id, keyword);
changed |= self.push_common(decl, importance, overwrite_more_important);
}
}
AllShorthand::WithVariables(unparsed) => {
for &id in ShorthandId::All.longhands() {
let decl = PropertyDeclaration::WithVariables(id, unparsed.clone());
changed |= self.push_common(decl, importance, overwrite_more_important);
}
}
}
changed
}
/// Adds or overrides the declaration for a given property in this block,
/// **except** if an existing declaration for the same property is more important.
pub fn push(&mut self, declaration: PropertyDeclaration, importance: Importance) { pub fn push(&mut self, declaration: PropertyDeclaration, importance: Importance) {
self.push_common(declaration, importance, false); self.push_common(declaration, importance, false);
} }
/// Implementation detail of push and ParsedDeclaration::expand* fn push_common(&mut self, declaration: PropertyDeclaration, importance: Importance,
pub fn push_common(&mut self, declaration: PropertyDeclaration, importance: Importance,
overwrite_more_important: bool) -> bool { overwrite_more_important: bool) -> bool {
let definitely_new = if let PropertyDeclarationId::Longhand(id) = declaration.id() { let definitely_new = if let PropertyDeclarationId::Longhand(id) = declaration.id() {
!self.longhands.contains(id) !self.longhands.contains(id)
@ -657,15 +705,15 @@ pub fn parse_style_attribute(input: &str,
/// Parse a given property declaration. Can result in multiple /// Parse a given property declaration. Can result in multiple
/// `PropertyDeclaration`s when expanding a shorthand, for example. /// `PropertyDeclaration`s when expanding a shorthand, for example.
/// ///
/// The vector returned will not have the importance set; /// This does not attempt to parse !important at all.
/// this does not attempt to parse !important at all pub fn parse_one_declaration_into(declarations: &mut SourcePropertyDeclaration,
pub fn parse_one_declaration(id: PropertyId, id: PropertyId,
input: &str, input: &str,
url_data: &UrlExtraData, url_data: &UrlExtraData,
error_reporter: &ParseErrorReporter, error_reporter: &ParseErrorReporter,
parsing_mode: ParsingMode, parsing_mode: ParsingMode,
quirks_mode: QuirksMode) quirks_mode: QuirksMode)
-> Result<ParsedDeclaration, ()> { -> Result<(), ()> {
let context = ParserContext::new(Origin::Author, let context = ParserContext::new(Origin::Author,
url_data, url_data,
error_reporter, error_reporter,
@ -673,7 +721,7 @@ pub fn parse_one_declaration(id: PropertyId,
parsing_mode, parsing_mode,
quirks_mode); quirks_mode);
Parser::new(input).parse_entirely(|parser| { Parser::new(input).parse_entirely(|parser| {
ParsedDeclaration::parse(id, &context, parser) PropertyDeclaration::parse_into(declarations, id, &context, parser)
.map_err(|_| ()) .map_err(|_| ())
}) })
} }
@ -681,24 +729,23 @@ pub fn parse_one_declaration(id: PropertyId,
/// A struct to parse property declarations. /// A struct to parse property declarations.
struct PropertyDeclarationParser<'a, 'b: 'a> { struct PropertyDeclarationParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>, context: &'a ParserContext<'b>,
declarations: &'a mut SourcePropertyDeclaration,
} }
/// Default methods reject all at rules. /// Default methods reject all at rules.
impl<'a, 'b> AtRuleParser for PropertyDeclarationParser<'a, 'b> { impl<'a, 'b> AtRuleParser for PropertyDeclarationParser<'a, 'b> {
type Prelude = (); type Prelude = ();
type AtRule = (ParsedDeclaration, Importance); type AtRule = Importance;
} }
impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> { impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
type Declaration = (ParsedDeclaration, Importance); type Declaration = Importance;
fn parse_value(&mut self, name: &str, input: &mut Parser) fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<Importance, ()> {
-> Result<(ParsedDeclaration, Importance), ()> {
let id = try!(PropertyId::parse(name.into())); let id = try!(PropertyId::parse(name.into()));
let parsed = input.parse_until_before(Delimiter::Bang, |input| { input.parse_until_before(Delimiter::Bang, |input| {
ParsedDeclaration::parse(id, self.context, input) PropertyDeclaration::parse_into(self.declarations, id, self.context, input)
.map_err(|_| ()) .map_err(|_| ())
})?; })?;
let importance = match input.try(parse_important) { let importance = match input.try(parse_important) {
@ -706,10 +753,8 @@ impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
Err(()) => Importance::Normal, Err(()) => Importance::Normal,
}; };
// In case there is still unparsed text in the declaration, we should roll back. // In case there is still unparsed text in the declaration, we should roll back.
if !input.is_exhausted() { input.expect_exhausted()?;
return Err(()) Ok(importance)
}
Ok((parsed, importance))
} }
} }
@ -719,15 +764,20 @@ impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
pub fn parse_property_declaration_list(context: &ParserContext, pub fn parse_property_declaration_list(context: &ParserContext,
input: &mut Parser) input: &mut Parser)
-> PropertyDeclarationBlock { -> PropertyDeclarationBlock {
let mut declarations = SourcePropertyDeclaration::new();
let mut block = PropertyDeclarationBlock::new(); let mut block = PropertyDeclarationBlock::new();
let parser = PropertyDeclarationParser { let parser = PropertyDeclarationParser {
context: context, context: context,
declarations: &mut declarations,
}; };
let mut iter = DeclarationListParser::new(input, parser); let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() { while let Some(declaration) = iter.next() {
match declaration { match declaration {
Ok((parsed, importance)) => parsed.expand_push_into(&mut block, importance), Ok(importance) => {
block.extend(iter.parser.declarations.drain(), importance);
}
Err(range) => { Err(range) => {
iter.parser.declarations.clear();
let pos = range.start; let pos = range.start;
let message = format!("Unsupported property declaration: '{}'", let message = format!("Unsupported property declaration: '{}'",
iter.input.slice(range)); iter.input.slice(range));

View file

@ -748,15 +748,23 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use cssparser::Parser; use cssparser::Parser;
use parser::ParserContext; use parser::ParserContext;
use properties::{PropertyDeclaration, ParsedDeclaration}; use properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed};
use properties::{ShorthandId, UnparsedValue, longhands}; use properties::{ShorthandId, LonghandId, UnparsedValue, longhands};
use std::fmt; use std::fmt;
use stylearc::Arc; use stylearc::Arc;
use style_traits::ToCss; use style_traits::ToCss;
pub struct Longhands { pub struct Longhands {
% for sub_property in shorthand.sub_properties: % for sub_property in shorthand.sub_properties:
pub ${sub_property.ident}: longhands::${sub_property.ident}::SpecifiedValue, pub ${sub_property.ident}:
% if sub_property.boxed:
Box<
% endif
longhands::${sub_property.ident}::SpecifiedValue
% if sub_property.boxed:
>
% endif
,
% endfor % endfor
} }
@ -816,7 +824,8 @@
/// Parse the given shorthand and fill the result into the /// Parse the given shorthand and fill the result into the
/// `declarations` vector. /// `declarations` vector.
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<ParsedDeclaration, ()> { pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
context: &ParserContext, input: &mut Parser) -> Result<(), ()> {
input.look_for_var_functions(); input.look_for_var_functions();
let start = input.position(); let start = input.position();
let value = input.parse_entirely(|input| parse_value(context, input)); let value = input.parse_entirely(|input| parse_value(context, input));
@ -825,17 +834,29 @@
} }
let var = input.seen_var_functions(); let var = input.seen_var_functions();
if let Ok(value) = value { if let Ok(value) = value {
Ok(ParsedDeclaration::${shorthand.camel_case}(value)) % for sub_property in shorthand.sub_properties:
declarations.push(PropertyDeclaration::${sub_property.camel_case}(
value.${sub_property.ident}
));
% endfor
Ok(())
} else if var { } else if var {
input.reset(start); input.reset(start);
let (first_token_type, css) = try!( let (first_token_type, css) = try!(
::custom_properties::parse_non_custom_with_var(input)); ::custom_properties::parse_non_custom_with_var(input));
Ok(ParsedDeclaration::${shorthand.camel_case}WithVariables(Arc::new(UnparsedValue { let unparsed = Arc::new(UnparsedValue {
css: css.into_owned(), css: css.into_owned(),
first_token_type: first_token_type, first_token_type: first_token_type,
url_data: context.url_data.clone(), url_data: context.url_data.clone(),
from_shorthand: Some(ShorthandId::${shorthand.camel_case}), from_shorthand: Some(ShorthandId::${shorthand.camel_case}),
}))) });
% for sub_property in shorthand.sub_properties:
declarations.push(PropertyDeclaration::WithVariables(
LonghandId::${sub_property.camel_case},
unparsed.clone()
));
% endfor
Ok(())
} else { } else {
Err(()) Err(())
} }
@ -865,7 +886,7 @@
try!(parse_four_sides(input, ${parser_function})); try!(parse_four_sides(input, ${parser_function}));
let _unused = context; let _unused = context;
% endif % endif
Ok(Longhands { Ok(expanded! {
% for side in ["top", "right", "bottom", "left"]: % for side in ["top", "right", "bottom", "left"]:
${to_rust_ident(sub_property_pattern % side)}: ${side}, ${to_rust_ident(sub_property_pattern % side)}: ${side},
% endfor % endfor

View file

@ -13,6 +13,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt; use std::fmt;
use std::mem;
use std::ops::Deref; use std::ops::Deref;
use stylearc::{Arc, UniqueArc}; use stylearc::{Arc, UniqueArc};
@ -58,6 +59,35 @@ macro_rules! property_name {
#[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"] #[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"]
pub mod declaration_block; pub mod declaration_block;
/// Conversion with fewer impls than From/Into
pub trait MaybeBoxed<Out> {
/// Convert
fn maybe_boxed(self) -> Out;
}
impl<T> MaybeBoxed<T> for T {
#[inline]
fn maybe_boxed(self) -> T { self }
}
impl<T> MaybeBoxed<Box<T>> for T {
#[inline]
fn maybe_boxed(self) -> Box<T> { Box::new(self) }
}
macro_rules! expanded {
( $( $name: ident: $value: expr ),+ ) => {
expanded!( $( $name: $value, )+ )
};
( $( $name: ident: $value: expr, )+ ) => {
Longhands {
$(
$name: MaybeBoxed::maybe_boxed($value),
)+
}
}
}
/// A module with all the code for longhand properties. /// A module with all the code for longhand properties.
#[allow(missing_docs)] #[allow(missing_docs)]
pub mod longhands { pub mod longhands {
@ -177,10 +207,11 @@ pub mod shorthands {
pub mod all { pub mod all {
use cssparser::Parser; use cssparser::Parser;
use parser::ParserContext; use parser::ParserContext;
use properties::{ParsedDeclaration, ShorthandId, UnparsedValue}; use properties::{SourcePropertyDeclaration, AllShorthand, ShorthandId, UnparsedValue};
use stylearc::Arc; use stylearc::Arc;
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<ParsedDeclaration, ()> { pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
context: &ParserContext, input: &mut Parser) -> Result<(), ()> {
// This function is like the parse() that is generated by // This function is like the parse() that is generated by
// helpers:shorthand, but since the only values for the 'all' // helpers:shorthand, but since the only values for the 'all'
// shorthand when not just a single CSS-wide keyword is one // shorthand when not just a single CSS-wide keyword is one
@ -196,12 +227,13 @@ pub mod shorthands {
input.reset(start); input.reset(start);
let (first_token_type, css) = try!( let (first_token_type, css) = try!(
::custom_properties::parse_non_custom_with_var(input)); ::custom_properties::parse_non_custom_with_var(input));
Ok(ParsedDeclaration::AllWithVariables(Arc::new(UnparsedValue { declarations.all_shorthand = AllShorthand::WithVariables(Arc::new(UnparsedValue {
css: css.into_owned(), css: css.into_owned(),
first_token_type: first_token_type, first_token_type: first_token_type,
url_data: context.url_data.clone(), url_data: context.url_data.clone(),
from_shorthand: Some(ShorthandId::All), from_shorthand: Some(ShorthandId::All),
}))) }));
Ok(())
} else { } else {
Err(()) Err(())
} }
@ -405,11 +437,7 @@ impl PropertyDeclarationIdSet {
Some(ShorthandId::${shorthand.camel_case}) => { Some(ShorthandId::${shorthand.camel_case}) => {
shorthands::${shorthand.ident}::parse_value(&context, input) shorthands::${shorthand.ident}::parse_value(&context, input)
.map(|result| { .map(|result| {
% if property.boxed:
DeclaredValueOwned::Value(Box::new(result.${property.ident}))
% else:
DeclaredValueOwned::Value(result.${property.ident}) DeclaredValueOwned::Value(result.${property.ident})
% endif
}) })
} }
% endif % endif
@ -976,220 +1004,6 @@ impl PropertyId {
} }
} }
/// Includes shorthands before expansion
pub enum ParsedDeclaration {
% for shorthand in data.shorthands:
% if shorthand.name == "all":
// No need for an All(shorthands::all::Longhands) case, since we can
// never have any values for 'all' other than the CSS-wide keywords
// and values with variable references.
% else:
/// ${shorthand.name}
${shorthand.camel_case}(shorthands::${shorthand.ident}::Longhands),
% endif
/// ${shorthand.name} with a CSS-wide keyword
${shorthand.camel_case}CSSWideKeyword(CSSWideKeyword),
/// ${shorthand.name} with var() functions
${shorthand.camel_case}WithVariables(Arc<UnparsedValue>),
% endfor
/// Not a shorthand
LonghandOrCustom(PropertyDeclaration),
}
impl ParsedDeclaration {
/// Transform this ParsedDeclaration into a sequence of PropertyDeclaration
/// by expanding shorthand declarations into their corresponding longhands
///
/// Adds or overrides exsting declarations in the given block,
/// except if existing declarations are more important.
#[inline]
pub fn expand_push_into(self, block: &mut PropertyDeclarationBlock,
importance: Importance) {
self.expand_into(block, importance, false);
}
/// Transform this ParsedDeclaration into a sequence of PropertyDeclaration
/// by expanding shorthand declarations into their corresponding longhands
///
/// Add or override existing declarations in the given block.
/// Return whether anything changed.
#[inline]
pub fn expand_set_into(self, block: &mut PropertyDeclarationBlock,
importance: Importance) -> bool {
self.expand_into(block, importance, true)
}
fn expand_into(self, block: &mut PropertyDeclarationBlock,
importance: Importance,
overwrite_more_important: bool) -> bool {
match self {
% for shorthand in data.shorthands:
% if shorthand.name != "all":
ParsedDeclaration::${shorthand.camel_case}(
shorthands::${shorthand.ident}::Longhands {
% for sub_property in shorthand.sub_properties:
${sub_property.ident},
% endfor
}
) => {
let mut changed = false;
% for sub_property in shorthand.sub_properties:
changed |= block.push_common(
PropertyDeclaration::${sub_property.camel_case}(
% if sub_property.boxed:
Box::new(${sub_property.ident})
% else:
${sub_property.ident}
% endif
),
importance,
overwrite_more_important,
);
% endfor
changed
},
% endif
ParsedDeclaration::${shorthand.camel_case}CSSWideKeyword(keyword) => {
let mut changed = false;
% for sub_property in shorthand.sub_properties:
changed |= block.push_common(
PropertyDeclaration::CSSWideKeyword(
LonghandId::${sub_property.camel_case},
keyword,
),
importance,
overwrite_more_important,
);
% endfor
changed
},
ParsedDeclaration::${shorthand.camel_case}WithVariables(value) => {
debug_assert_eq!(
value.from_shorthand,
Some(ShorthandId::${shorthand.camel_case})
);
let mut changed = false;
% for sub_property in shorthand.sub_properties:
changed |= block.push_common(
PropertyDeclaration::WithVariables(
LonghandId::${sub_property.camel_case},
value.clone()
),
importance,
overwrite_more_important,
);
% endfor
changed
}
% endfor
ParsedDeclaration::LonghandOrCustom(declaration) => {
block.push_common(declaration, importance, overwrite_more_important)
}
}
}
/// 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.
///
/// This will not actually parse Importance values, and will always set things
/// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
/// we only set them here so that we don't have to reallocate
pub fn parse(id: PropertyId, context: &ParserContext, input: &mut Parser)
-> Result<ParsedDeclaration, PropertyDeclarationParseError> {
let rule_type = context.rule_type();
debug_assert!(rule_type == CssRuleType::Keyframe ||
rule_type == CssRuleType::Page ||
rule_type == CssRuleType::Style,
"Declarations are only expected inside a keyframe, page, or style rule.");
match id {
PropertyId::Custom(name) => {
let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword),
Err(()) => match ::custom_properties::SpecifiedValue::parse(context, input) {
Ok(value) => DeclaredValueOwned::Value(value),
Err(()) => return Err(PropertyDeclarationParseError::InvalidValue),
}
};
Ok(ParsedDeclaration::LonghandOrCustom(PropertyDeclaration::Custom(name, value)))
}
PropertyId::Longhand(id) => match id {
% for property in data.longhands:
LonghandId::${property.camel_case} => {
% if not property.derived_from:
% if not property.allowed_in_keyframe_block:
if rule_type == CssRuleType::Keyframe {
return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
}
% endif
% if property.internal:
if context.stylesheet_origin != Origin::UserAgent {
return Err(PropertyDeclarationParseError::UnknownProperty)
}
% endif
% if not property.allowed_in_page_rule:
if rule_type == CssRuleType::Page {
return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
}
% endif
${property_pref_check(property)}
match longhands::${property.ident}::parse_declared(context, input) {
Ok(value) => {
Ok(ParsedDeclaration::LonghandOrCustom(value))
},
Err(()) => Err(PropertyDeclarationParseError::InvalidValue),
}
% else:
Err(PropertyDeclarationParseError::UnknownProperty)
% endif
}
% endfor
},
PropertyId::Shorthand(id) => match id {
% for shorthand in data.shorthands:
ShorthandId::${shorthand.camel_case} => {
% if not shorthand.allowed_in_keyframe_block:
if rule_type == CssRuleType::Keyframe {
return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
}
% endif
% if shorthand.internal:
if context.stylesheet_origin != Origin::UserAgent {
return Err(PropertyDeclarationParseError::UnknownProperty)
}
% endif
% if not shorthand.allowed_in_page_rule:
if rule_type == CssRuleType::Page {
return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
}
% endif
${property_pref_check(shorthand)}
match input.try(|i| CSSWideKeyword::parse(context, i)) {
Ok(keyword) => {
Ok(ParsedDeclaration::${shorthand.camel_case}CSSWideKeyword(keyword))
},
Err(()) => {
shorthands::${shorthand.ident}::parse(context, input)
.map_err(|()| PropertyDeclarationParseError::InvalidValue)
}
}
}
% endfor
}
}
}
}
/// Servo's representation for a property declaration. /// Servo's representation for a property declaration.
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum PropertyDeclaration { pub enum PropertyDeclaration {
@ -1483,6 +1297,180 @@ impl PropertyDeclaration {
PropertyDeclaration::Custom(..) => false, PropertyDeclaration::Custom(..) => false,
} }
} }
/// The `context` 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.
///
/// This will not actually parse Importance values, and will always set things
/// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
/// we only set them here so that we don't have to reallocate
pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
id: PropertyId, context: &ParserContext, input: &mut Parser)
-> Result<(), PropertyDeclarationParseError> {
assert!(declarations.is_empty());
let rule_type = context.rule_type();
debug_assert!(rule_type == CssRuleType::Keyframe ||
rule_type == CssRuleType::Page ||
rule_type == CssRuleType::Style,
"Declarations are only expected inside a keyframe, page, or style rule.");
match id {
PropertyId::Custom(name) => {
let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword),
Err(()) => match ::custom_properties::SpecifiedValue::parse(context, input) {
Ok(value) => DeclaredValueOwned::Value(value),
Err(()) => return Err(PropertyDeclarationParseError::InvalidValue),
}
};
declarations.push(PropertyDeclaration::Custom(name, value));
Ok(())
}
PropertyId::Longhand(id) => match id {
% for property in data.longhands:
LonghandId::${property.camel_case} => {
% if not property.derived_from:
% if not property.allowed_in_keyframe_block:
if rule_type == CssRuleType::Keyframe {
return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
}
% endif
% if property.internal:
if context.stylesheet_origin != Origin::UserAgent {
return Err(PropertyDeclarationParseError::UnknownProperty)
}
% endif
% if not property.allowed_in_page_rule:
if rule_type == CssRuleType::Page {
return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
}
% endif
${property_pref_check(property)}
match longhands::${property.ident}::parse_declared(context, input) {
Ok(value) => {
declarations.push(value);
Ok(())
},
Err(()) => Err(PropertyDeclarationParseError::InvalidValue),
}
% else:
Err(PropertyDeclarationParseError::UnknownProperty)
% endif
}
% endfor
},
PropertyId::Shorthand(id) => match id {
% for shorthand in data.shorthands:
ShorthandId::${shorthand.camel_case} => {
% if not shorthand.allowed_in_keyframe_block:
if rule_type == CssRuleType::Keyframe {
return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
}
% endif
% if shorthand.internal:
if context.stylesheet_origin != Origin::UserAgent {
return Err(PropertyDeclarationParseError::UnknownProperty)
}
% endif
% if not shorthand.allowed_in_page_rule:
if rule_type == CssRuleType::Page {
return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
}
% endif
${property_pref_check(shorthand)}
match input.try(|i| CSSWideKeyword::parse(context, i)) {
Ok(keyword) => {
% if shorthand.name == "all":
declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword);
% else:
% for sub_property in shorthand.sub_properties:
declarations.push(PropertyDeclaration::CSSWideKeyword(
LonghandId::${sub_property.camel_case},
keyword,
));
% endfor
% endif
Ok(())
},
Err(()) => {
shorthands::${shorthand.ident}::parse_into(declarations, context, input)
.map_err(|()| PropertyDeclarationParseError::InvalidValue)
}
}
}
% endfor
}
}
}
}
const MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL: usize =
${max(len(s.sub_properties) for s in data.shorthands_except_all())};
type SourcePropertyDeclarationArray =
[PropertyDeclaration; MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL];
/// A stack-allocated vector of `PropertyDeclaration`
/// large enough to parse one CSS `key: value` declaration.
/// (Shorthands expand to multiple `PropertyDeclaration`s.)
pub struct SourcePropertyDeclaration {
declarations: ::arrayvec::ArrayVec<SourcePropertyDeclarationArray>,
/// Stored separately to keep MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL smaller.
all_shorthand: AllShorthand,
}
impl SourcePropertyDeclaration {
/// Create one. Its big, try not to move it around.
#[inline]
pub fn new() -> Self {
SourcePropertyDeclaration {
declarations: ::arrayvec::ArrayVec::new(),
all_shorthand: AllShorthand::NotSet,
}
}
/// Similar to Vec::drain: leaves this empty when the return value is dropped.
pub fn drain(&mut self) -> SourcePropertyDeclarationDrain {
SourcePropertyDeclarationDrain {
declarations: self.declarations.drain(..),
all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet),
}
}
/// Reset to initial state
pub fn clear(&mut self) {
self.declarations.clear();
self.all_shorthand = AllShorthand::NotSet;
}
fn is_empty(&self) -> bool {
self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet)
}
fn push(&mut self, declaration: PropertyDeclaration) {
let over_capacity = self.declarations.push(declaration).is_some();
debug_assert!(!over_capacity);
}
}
/// Return type of SourcePropertyDeclaration::drain
pub struct SourcePropertyDeclarationDrain<'a> {
declarations: ::arrayvec::Drain<'a, SourcePropertyDeclarationArray>,
all_shorthand: AllShorthand,
}
enum AllShorthand {
NotSet,
CSSWideKeyword(CSSWideKeyword),
WithVariables(Arc<UnparsedValue>)
} }
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]

View file

@ -108,7 +108,7 @@
} }
})); }));
Ok(Longhands { Ok(expanded! {
background_color: background_color.unwrap_or(CSSColor::transparent()), background_color: background_color.unwrap_or(CSSColor::transparent()),
background_image: background_image, background_image: background_image,
background_position_x: background_position_x, background_position_x: background_position_x,
@ -207,7 +207,7 @@
return Err(()); return Err(());
} }
Ok(Longhands { Ok(expanded! {
background_position_x: position_x, background_position_x: position_x,
background_position_y: position_y, background_position_y: position_y,
}) })

View file

@ -24,7 +24,7 @@ ${helpers.four_sides_shorthand("border-style", "border-%s-style",
let (top, right, bottom, left) = try!(parse_four_sides(input, |i| { let (top, right, bottom, left) = try!(parse_four_sides(input, |i| {
BorderWidth::parse_quirky(context, i, AllowQuirks::Yes) BorderWidth::parse_quirky(context, i, AllowQuirks::Yes)
})); }));
Ok(Longhands { Ok(expanded! {
% for side in PHYSICAL_SIDES: % for side in PHYSICAL_SIDES:
${to_rust_ident('border-%s-width' % side)}: ${side}, ${to_rust_ident('border-%s-width' % side)}: ${side},
% endfor % endfor
@ -103,7 +103,7 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser)
pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> { pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
let (color, style, width) = try!(super::parse_border(context, input)); let (color, style, width) = try!(super::parse_border(context, input));
Ok(Longhands { Ok(expanded! {
border_${to_rust_ident(side)}_color: color, border_${to_rust_ident(side)}_color: color,
border_${to_rust_ident(side)}_style: style, border_${to_rust_ident(side)}_style: style,
border_${to_rust_ident(side)}_width: width border_${to_rust_ident(side)}_width: width
@ -144,7 +144,7 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser)
use properties::longhands::{border_image_source, border_image_width}; use properties::longhands::{border_image_source, border_image_width};
let (color, style, width) = try!(super::parse_border(context, input)); let (color, style, width) = try!(super::parse_border(context, input));
Ok(Longhands { Ok(expanded! {
% for side in PHYSICAL_SIDES: % for side in PHYSICAL_SIDES:
border_${side}_color: color.clone(), border_${side}_color: color.clone(),
border_${side}_style: style, border_${side}_style: style,
@ -211,7 +211,7 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser)
pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> { pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
let radii = try!(BorderRadius::parse(context, input)); let radii = try!(BorderRadius::parse(context, input));
Ok(Longhands { Ok(expanded! {
border_top_left_radius: radii.top_left, border_top_left_radius: radii.top_left,
border_top_right_radius: radii.top_right, border_top_right_radius: radii.top_right,
border_bottom_right_radius: radii.bottom_right, border_bottom_right_radius: radii.bottom_right,
@ -303,7 +303,7 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser)
} }
})); }));
Ok(Longhands { Ok(expanded! {
% for name in "outset repeat slice source width".split(): % for name in "outset repeat slice source width".split():
border_image_${name}: border_image_${name}, border_image_${name}: border_image_${name},
% endfor % endfor

View file

@ -16,19 +16,19 @@
let moz_kw_found = input.try(|i| match_ignore_ascii_case! { let moz_kw_found = input.try(|i| match_ignore_ascii_case! {
&i.expect_ident()?, &i.expect_ident()?,
"-moz-scrollbars-horizontal" => { "-moz-scrollbars-horizontal" => {
Ok(Longhands { Ok(expanded! {
overflow_x: SpecifiedValue::scroll, overflow_x: SpecifiedValue::scroll,
overflow_y: SpecifiedValue::hidden, overflow_y: SpecifiedValue::hidden,
}) })
} }
"-moz-scrollbars-vertical" => { "-moz-scrollbars-vertical" => {
Ok(Longhands { Ok(expanded! {
overflow_x: SpecifiedValue::hidden, overflow_x: SpecifiedValue::hidden,
overflow_y: SpecifiedValue::scroll, overflow_y: SpecifiedValue::scroll,
}) })
} }
"-moz-scrollbars-none" => { "-moz-scrollbars-none" => {
Ok(Longhands { Ok(expanded! {
overflow_x: SpecifiedValue::hidden, overflow_x: SpecifiedValue::hidden,
overflow_y: SpecifiedValue::hidden, overflow_y: SpecifiedValue::hidden,
}) })
@ -40,7 +40,7 @@
} }
% endif % endif
let overflow = try!(parse_overflow(context, input)); let overflow = try!(parse_overflow(context, input));
Ok(Longhands { Ok(expanded! {
overflow_x: overflow, overflow_x: overflow,
overflow_y: overflow, overflow_y: overflow,
}) })
@ -148,7 +148,7 @@ macro_rules! try_parse_one {
% endfor % endfor
} }
Ok(Longhands { Ok(expanded! {
% for prop in "property duration timing_function delay".split(): % for prop in "property duration timing_function delay".split():
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s), transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s),
% endfor % endfor
@ -259,7 +259,7 @@ macro_rules! try_parse_one {
% endfor % endfor
} }
Ok(Longhands { Ok(expanded! {
% for prop in props: % for prop in props:
animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s), animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s),
% endfor % endfor
@ -305,7 +305,7 @@ macro_rules! try_parse_one {
pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> { pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
let result = try!(scroll_snap_type_x::parse(context, input)); let result = try!(scroll_snap_type_x::parse(context, input));
Ok(Longhands { Ok(expanded! {
scroll_snap_type_x: result, scroll_snap_type_x: result,
scroll_snap_type_y: result, scroll_snap_type_y: result,
}) })
@ -332,7 +332,7 @@ macro_rules! try_parse_one {
use properties::longhands::transform; use properties::longhands::transform;
pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> { pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
Ok(Longhands { Ok(expanded! {
transform: transform::parse_prefixed(context, input)?, transform: transform::parse_prefixed(context, input)?,
}) })
} }

View file

@ -42,7 +42,7 @@
if values == 0 || values > 2 { if values == 0 || values > 2 {
Err(()) Err(())
} else { } else {
Ok(Longhands { Ok(expanded! {
column_count: unwrap_or_initial!(column_count), column_count: unwrap_or_initial!(column_count),
column_width: unwrap_or_initial!(column_width), column_width: unwrap_or_initial!(column_width),
}) })
@ -86,7 +86,7 @@
break break
} }
if any { if any {
Ok(Longhands { Ok(expanded! {
column_rule_width: unwrap_or_initial!(column_rule_width), column_rule_width: unwrap_or_initial!(column_rule_width),
column_rule_style: unwrap_or_initial!(column_rule_style), column_rule_style: unwrap_or_initial!(column_rule_style),
column_rule_color: unwrap_or_initial!(column_rule_color), column_rule_color: unwrap_or_initial!(column_rule_color),

View file

@ -44,7 +44,7 @@
let size; let size;
% if product == "gecko": % if product == "gecko":
if let Ok(sys) = input.try(SystemFont::parse) { if let Ok(sys) = input.try(SystemFont::parse) {
return Ok(Longhands { return Ok(expanded! {
% for name in SYSTEM_FONT_LONGHANDS: % for name in SYSTEM_FONT_LONGHANDS:
${name}: ${name}::SpecifiedValue::system_font(sys), ${name}: ${name}::SpecifiedValue::system_font(sys),
% endfor % endfor
@ -102,7 +102,7 @@
None None
}; };
let family = FontFamily::parse(input)?; let family = FontFamily::parse(input)?;
Ok(Longhands { Ok(expanded! {
% for name in "style weight stretch size variant_caps".split(): % for name in "style weight stretch size variant_caps".split():
font_${name}: unwrap_or_initial!(font_${name}, ${name}), font_${name}: unwrap_or_initial!(font_${name}, ${name}),
% endfor % endfor
@ -236,7 +236,7 @@
if count == 0 || count > 1 { if count == 0 || count > 1 {
return Err(()) return Err(())
} }
Ok(Longhands { Ok(expanded! {
font_variant_caps: unwrap_or_initial!(font_variant_caps, caps), font_variant_caps: unwrap_or_initial!(font_variant_caps, caps),
// FIXME: Bug 1356134 - parse all sub properties. // FIXME: Bug 1356134 - parse all sub properties.
% if product == "gecko" or data.testing: % if product == "gecko" or data.testing:

View file

@ -13,7 +13,7 @@
use parser::Parse; use parser::Parse;
let url = UrlOrNone::parse(context, input)?; let url = UrlOrNone::parse(context, input)?;
Ok(Longhands { Ok(expanded! {
marker_start: url.clone(), marker_start: url.clone(),
marker_mid: url.clone(), marker_mid: url.clone(),
marker_end: url, marker_end: url,

View file

@ -29,7 +29,7 @@
break break
} }
if color.is_some() || style.is_some() { if color.is_some() || style.is_some() {
Ok(Longhands { Ok(expanded! {
text_emphasis_color: unwrap_or_initial!(text_emphasis_color, color), text_emphasis_color: unwrap_or_initial!(text_emphasis_color, color),
text_emphasis_style: unwrap_or_initial!(text_emphasis_style, style), text_emphasis_style: unwrap_or_initial!(text_emphasis_style, style),
}) })
@ -77,7 +77,7 @@
} }
if color.is_some() || width.is_some() { if color.is_some() || width.is_some() {
Ok(Longhands { Ok(expanded! {
_webkit_text_stroke_color: unwrap_or_initial!(_webkit_text_stroke_color, color), _webkit_text_stroke_color: unwrap_or_initial!(_webkit_text_stroke_color, color),
_webkit_text_stroke_width: unwrap_or_initial!(_webkit_text_stroke_width, width), _webkit_text_stroke_width: unwrap_or_initial!(_webkit_text_stroke_width, width),
}) })

View file

@ -70,35 +70,35 @@
// long as we parsed something. // long as we parsed something.
match (any, nones, list_style_type, image) { match (any, nones, list_style_type, image) {
(true, 2, None, None) => { (true, 2, None, None) => {
Ok(Longhands { Ok(expanded! {
list_style_position: position, list_style_position: position,
list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)), list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)),
list_style_type: list_style_type_none(), list_style_type: list_style_type_none(),
}) })
} }
(true, 1, None, Some(image)) => { (true, 1, None, Some(image)) => {
Ok(Longhands { Ok(expanded! {
list_style_position: position, list_style_position: position,
list_style_image: image, list_style_image: image,
list_style_type: list_style_type_none(), list_style_type: list_style_type_none(),
}) })
} }
(true, 1, Some(list_style_type), None) => { (true, 1, Some(list_style_type), None) => {
Ok(Longhands { Ok(expanded! {
list_style_position: position, list_style_position: position,
list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)), list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)),
list_style_type: list_style_type, list_style_type: list_style_type,
}) })
} }
(true, 1, None, None) => { (true, 1, None, None) => {
Ok(Longhands { Ok(expanded! {
list_style_position: position, list_style_position: position,
list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)), list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)),
list_style_type: list_style_type_none(), list_style_type: list_style_type_none(),
}) })
} }
(true, 0, list_style_type, image) => { (true, 0, list_style_type, image) => {
Ok(Longhands { Ok(expanded! {
list_style_position: position, list_style_position: position,
list_style_image: unwrap_or_initial!(list_style_image, image), list_style_image: unwrap_or_initial!(list_style_image, image),
list_style_type: unwrap_or_initial!(list_style_type), list_style_type: unwrap_or_initial!(list_style_type),

View file

@ -107,7 +107,7 @@
} }
})); }));
Ok(Longhands { Ok(expanded! {
% for name in "image mode position_x position_y size repeat origin clip composite".split(): % for name in "image mode position_x position_y size repeat origin clip composite".split():
mask_${name}: mask_${name}, mask_${name}: mask_${name},
% endfor % endfor
@ -196,7 +196,7 @@
return Err(()); return Err(());
} }
Ok(Longhands { Ok(expanded! {
mask_position_x: position_x, mask_position_x: position_x,
mask_position_y: position_y, mask_position_y: position_y,
}) })

View file

@ -41,7 +41,7 @@
break break
} }
if any { if any {
Ok(Longhands { Ok(expanded! {
outline_color: unwrap_or_initial!(outline_color, color), outline_color: unwrap_or_initial!(outline_color, color),
outline_style: unwrap_or_initial!(outline_style, style), outline_style: unwrap_or_initial!(outline_style, style),
outline_width: unwrap_or_initial!(outline_width, width), outline_width: unwrap_or_initial!(outline_width, width),
@ -73,7 +73,7 @@
pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> { pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
// Re-use border-radius parsing. // Re-use border-radius parsing.
shorthands::border_radius::parse_value(context, input).map(|longhands| { shorthands::border_radius::parse_value(context, input).map(|longhands| {
Longhands { expanded! {
% for corner in ["top_left", "top_right", "bottom_right", "bottom_left"]: % for corner in ["top_left", "top_right", "bottom_right", "bottom_left"]:
_moz_outline_radius_${corner.replace("_", "")}: longhands.border_${corner}_radius, _moz_outline_radius_${corner.replace("_", "")}: longhands.border_${corner}_radius,
% endfor % endfor

View file

@ -30,7 +30,7 @@
if direction.is_none() && wrap.is_none() { if direction.is_none() && wrap.is_none() {
return Err(()) return Err(())
} }
Ok(Longhands { Ok(expanded! {
flex_direction: unwrap_or_initial!(flex_direction, direction), flex_direction: unwrap_or_initial!(flex_direction, direction),
flex_wrap: unwrap_or_initial!(flex_wrap, wrap), flex_wrap: unwrap_or_initial!(flex_wrap, wrap),
}) })
@ -63,7 +63,7 @@
let mut basis = None; let mut basis = None;
if input.try(|input| input.expect_ident_matching("none")).is_ok() { if input.try(|input| input.expect_ident_matching("none")).is_ok() {
return Ok(Longhands { return Ok(expanded! {
flex_grow: Number::new(0.0), flex_grow: Number::new(0.0),
flex_shrink: Number::new(0.0), flex_shrink: Number::new(0.0),
flex_basis: longhands::flex_basis::SpecifiedValue::auto(), flex_basis: longhands::flex_basis::SpecifiedValue::auto(),
@ -89,7 +89,7 @@
if grow.is_none() && basis.is_none() { if grow.is_none() && basis.is_none() {
return Err(()) return Err(())
} }
Ok(Longhands { Ok(expanded! {
flex_grow: grow.unwrap_or(Number::new(1.0)), flex_grow: grow.unwrap_or(Number::new(1.0)),
flex_shrink: shrink.unwrap_or(Number::new(1.0)), flex_shrink: shrink.unwrap_or(Number::new(1.0)),
flex_basis: basis.unwrap_or(longhands::flex_basis::SpecifiedValue::zero()), flex_basis: basis.unwrap_or(longhands::flex_basis::SpecifiedValue::zero()),
@ -118,7 +118,7 @@
let row_gap = grid_row_gap::parse(context, input)?; let row_gap = grid_row_gap::parse(context, input)?;
let column_gap = input.try(|input| grid_column_gap::parse(context, input)).unwrap_or(row_gap.clone()); let column_gap = input.try(|input| grid_column_gap::parse(context, input)).unwrap_or(row_gap.clone());
Ok(Longhands { Ok(expanded! {
grid_row_gap: row_gap, grid_row_gap: row_gap,
grid_column_gap: column_gap, grid_column_gap: column_gap,
}) })
@ -161,7 +161,7 @@
line line
}; };
Ok(Longhands { Ok(expanded! {
grid_${kind}_start: start, grid_${kind}_start: start,
grid_${kind}_end: end, grid_${kind}_end: end,
}) })
@ -219,7 +219,7 @@
(line.clone(), line.clone(), line) (line.clone(), line.clone(), line)
}; };
Ok(Longhands { Ok(expanded! {
grid_row_start: row_start, grid_row_start: row_start,
grid_row_end: row_end, grid_row_end: row_end,
grid_column_start: column_start, grid_column_start: column_start,
@ -258,7 +258,7 @@
return Err(()); return Err(());
} }
Ok(Longhands { Ok(expanded! {
align_content: align, align_content: align,
justify_content: justify, justify_content: justify,
}) })
@ -292,7 +292,7 @@
return Err(()); return Err(());
} }
Ok(Longhands { Ok(expanded! {
align_self: align, align_self: align,
justify_self: justify, justify_self: justify,
}) })
@ -334,7 +334,7 @@
return Err(()); return Err(());
} }
Ok(Longhands { Ok(expanded! {
align_items: align, align_items: align,
justify_items: justify, justify_items: justify,
}) })

View file

@ -50,7 +50,7 @@
return Err(()); return Err(());
} }
Ok(Longhands { Ok(expanded! {
text_decoration_line: unwrap_or_initial!(text_decoration_line, line), text_decoration_line: unwrap_or_initial!(text_decoration_line, line),
% if product == "gecko" or data.testing: % if product == "gecko" or data.testing:

View file

@ -6,7 +6,7 @@
use cssparser::{parse_important, Parser, Token}; use cssparser::{parse_important, Parser, Token};
use parser::ParserContext; use parser::ParserContext;
use properties::{PropertyId, ParsedDeclaration}; use properties::{PropertyId, PropertyDeclaration, SourcePropertyDeclaration};
use std::fmt; use std::fmt;
use style_traits::ToCss; use style_traits::ToCss;
use stylesheets::CssRuleType; use stylesheets::CssRuleType;
@ -213,7 +213,8 @@ impl Declaration {
}; };
let mut input = Parser::new(&self.val); let mut input = Parser::new(&self.val);
let context = ParserContext::new_with_rule_type(cx, Some(CssRuleType::Style)); let context = ParserContext::new_with_rule_type(cx, Some(CssRuleType::Style));
let res = ParsedDeclaration::parse(id, &context, &mut input); let mut declarations = SourcePropertyDeclaration::new();
let res = PropertyDeclaration::parse_into(&mut declarations, id, &context, &mut input);
let _ = input.try(parse_important); let _ = input.try(parse_important);
res.is_ok() && input.is_exhausted() res.is_ok() && input.is_exhausted()
} }

View file

@ -80,11 +80,11 @@ use style::keyframes::{Keyframe, KeyframeSelector, KeyframesStepValue};
use style::media_queries::{MediaList, parse_media_query_list}; use style::media_queries::{MediaList, parse_media_query_list};
use style::parallel; use style::parallel;
use style::parser::{PARSING_MODE_DEFAULT, ParserContext}; use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
use style::properties::{CascadeFlags, ComputedValues, Importance, ParsedDeclaration, StyleBuilder}; use style::properties::{CascadeFlags, ComputedValues, Importance, SourcePropertyDeclaration};
use style::properties::{LonghandIdSet, PropertyDeclarationBlock, PropertyId}; use style::properties::{LonghandIdSet, PropertyDeclarationBlock, PropertyId, StyleBuilder};
use style::properties::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP; use style::properties::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP;
use style::properties::animated_properties::{Animatable, AnimationValue, TransitionProperty}; use style::properties::animated_properties::{Animatable, AnimationValue, TransitionProperty};
use style::properties::parse_one_declaration; use style::properties::parse_one_declaration_into;
use style::restyle_hints::{self, RestyleHint}; use style::restyle_hints::{self, RestyleHint};
use style::rule_tree::StyleSource; use style::rule_tree::StyleSource;
use style::selector_parser::PseudoElementCascadeType; use style::selector_parser::PseudoElementCascadeType;
@ -1252,17 +1252,20 @@ pub extern "C" fn Servo_StyleSet_Drop(data: RawServoStyleSetOwned) {
let _ = data.into_box::<PerDocumentStyleData>(); let _ = data.into_box::<PerDocumentStyleData>();
} }
fn parse_property(property_id: PropertyId, fn parse_property_into(declarations: &mut SourcePropertyDeclaration,
property_id: PropertyId,
value: *const nsACString, value: *const nsACString,
data: *mut URLExtraData, data: *mut URLExtraData,
parsing_mode: structs::ParsingMode, parsing_mode: structs::ParsingMode,
quirks_mode: QuirksMode) -> Result<ParsedDeclaration, ()> { quirks_mode: QuirksMode) -> Result<(), ()> {
use style::parser::ParsingMode; use style::parser::ParsingMode;
let value = unsafe { value.as_ref().unwrap().as_str_unchecked() }; let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
let url_data = unsafe { RefPtr::from_ptr_ref(&data) }; let url_data = unsafe { RefPtr::from_ptr_ref(&data) };
let parsing_mode = ParsingMode::from_bits_truncate(parsing_mode); let parsing_mode = ParsingMode::from_bits_truncate(parsing_mode);
parse_one_declaration(property_id, parse_one_declaration_into(
declarations,
property_id,
value, value,
url_data, url_data,
&RustLogReporter, &RustLogReporter,
@ -1278,11 +1281,13 @@ pub extern "C" fn Servo_ParseProperty(property: nsCSSPropertyID, value: *const n
-> RawServoDeclarationBlockStrong { -> RawServoDeclarationBlockStrong {
let id = get_property_id_from_nscsspropertyid!(property, let id = get_property_id_from_nscsspropertyid!(property,
RawServoDeclarationBlockStrong::null()); RawServoDeclarationBlockStrong::null());
match parse_property(id, value, data, parsing_mode, quirks_mode.into()) { let mut declarations = SourcePropertyDeclaration::new();
Ok(parsed) => { match parse_property_into(&mut declarations, id, value, data,
parsing_mode, quirks_mode.into()) {
Ok(()) => {
let global_style_data = &*GLOBAL_STYLE_DATA; let global_style_data = &*GLOBAL_STYLE_DATA;
let mut block = PropertyDeclarationBlock::new(); let mut block = PropertyDeclarationBlock::new();
parsed.expand_push_into(&mut block, Importance::Normal); block.extend(declarations.drain(), Importance::Normal);
Arc::new(global_style_data.shared_lock.wrap(block)).into_strong() Arc::new(global_style_data.shared_lock.wrap(block)).into_strong()
} }
Err(_) => RawServoDeclarationBlockStrong::null() Err(_) => RawServoDeclarationBlockStrong::null()
@ -1439,11 +1444,13 @@ fn set_property(declarations: RawServoDeclarationBlockBorrowed, property_id: Pro
value: *const nsACString, is_important: bool, data: *mut URLExtraData, value: *const nsACString, is_important: bool, data: *mut URLExtraData,
parsing_mode: structs::ParsingMode, parsing_mode: structs::ParsingMode,
quirks_mode: QuirksMode) -> bool { quirks_mode: QuirksMode) -> bool {
match parse_property(property_id, value, data, parsing_mode, quirks_mode) { let mut source_declarations = SourcePropertyDeclaration::new();
Ok(parsed) => { match parse_property_into(&mut source_declarations, property_id, value, data,
parsing_mode, quirks_mode) {
Ok(()) => {
let importance = if is_important { Importance::Important } else { Importance::Normal }; let importance = if is_important { Importance::Important } else { Importance::Normal };
write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| { write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
parsed.expand_set_into(decls, importance) decls.extend_reset(source_declarations.drain(), importance)
}) })
}, },
Err(_) => false, Err(_) => false,
@ -1968,11 +1975,15 @@ pub extern "C" fn Servo_CSSSupports2(property: *const nsACString,
value: *const nsACString) -> bool { value: *const nsACString) -> bool {
let id = get_property_id_from_property!(property, false); let id = get_property_id_from_property!(property, false);
parse_property(id, let mut declarations = SourcePropertyDeclaration::new();
parse_property_into(
&mut declarations,
id,
value, value,
unsafe { DUMMY_URL_DATA }, unsafe { DUMMY_URL_DATA },
structs::ParsingMode_Default, structs::ParsingMode_Default,
QuirksMode::NoQuirks).is_ok() QuirksMode::NoQuirks
).is_ok()
} }
#[no_mangle] #[no_mangle]

View file

@ -288,9 +288,11 @@ class MachCommands(CommandBase):
@Command('test-stylo', @Command('test-stylo',
description='Run stylo unit tests', description='Run stylo unit tests',
category='testing') category='testing')
@CommandArgument('test_name', nargs=argparse.REMAINDER,
help="Only run tests that match this pattern or file path")
@CommandArgument('--release', default=False, action="store_true", @CommandArgument('--release', default=False, action="store_true",
help="Run with a release build of servo") help="Run with a release build of servo")
def test_stylo(self, release=False): def test_stylo(self, release=False, test_name=None):
self.set_use_stable_rust() self.set_use_stable_rust()
self.ensure_bootstrapped() self.ensure_bootstrapped()
@ -298,9 +300,10 @@ class MachCommands(CommandBase):
env["RUST_BACKTRACE"] = "1" env["RUST_BACKTRACE"] = "1"
env["CARGO_TARGET_DIR"] = path.join(self.context.topdir, "target", "geckolib").encode("UTF-8") env["CARGO_TARGET_DIR"] = path.join(self.context.topdir, "target", "geckolib").encode("UTF-8")
release = ["--release"] if release else [] args = (["cargo", "test", "-p", "stylo_tests", "--features", "testing"] +
(["--release"] if release else []) + (test_name or []))
with cd(path.join("ports", "geckolib")): with cd(path.join("ports", "geckolib")):
return call(["cargo", "test", "-p", "stylo_tests", "--features", "testing"] + release, env=env) return call(args, env=env)
@Command('test-compiletest', @Command('test-compiletest',
description='Run compiletests', description='Run compiletests',

View file

@ -4,22 +4,34 @@
use parsing::parse; use parsing::parse;
use style::parser::Parse; use style::parser::Parse;
use style::properties::MaybeBoxed;
use style::properties::longhands::{border_image_outset, border_image_repeat, border_image_slice}; use style::properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
use style::properties::longhands::{border_image_source, border_image_width}; use style::properties::longhands::{border_image_source, border_image_width};
use style::properties::shorthands::border_image; use style::properties::shorthands::border_image;
use style_traits::ToCss; use style_traits::ToCss;
macro_rules! assert_longhand {
($parsed_shorthand: expr, $prop: ident, $value_string: expr) => {
assert_eq!($parsed_shorthand.$prop, parse_longhand!($prop, $value_string).maybe_boxed())
}
}
macro_rules! assert_initial {
($parsed_shorthand: expr, $prop: ident) => {
assert_eq!($parsed_shorthand.$prop, $prop::get_initial_specified_value().maybe_boxed())
}
}
#[test] #[test]
fn border_image_shorthand_should_parse_when_all_properties_specified() { fn border_image_shorthand_should_parse_when_all_properties_specified() {
let input = "linear-gradient(red, blue) 30 30% 45 fill / 20px 40px / 10px round stretch"; let input = "linear-gradient(red, blue) 30 30% 45 fill / 20px 40px / 10px round stretch";
let result = parse(border_image::parse_value, input).unwrap(); let result = parse(border_image::parse_value, input).unwrap();
assert_eq!(result.border_image_source, assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
parse_longhand!(border_image_source, "linear-gradient(red, blue)")); assert_longhand!(result, border_image_slice, "30 30% 45 fill");
assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill")); assert_longhand!(result, border_image_width, "20px 40px");
assert_eq!(result.border_image_width, parse_longhand!(border_image_width, "20px 40px")); assert_longhand!(result, border_image_outset, "10px");
assert_eq!(result.border_image_outset, parse_longhand!(border_image_outset, "10px")); assert_longhand!(result, border_image_repeat, "round stretch");
assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round stretch"));
} }
#[test] #[test]
@ -27,12 +39,11 @@ fn border_image_shorthand_should_parse_without_width() {
let input = "linear-gradient(red, blue) 30 30% 45 fill / / 10px round stretch"; let input = "linear-gradient(red, blue) 30 30% 45 fill / / 10px round stretch";
let result = parse(border_image::parse_value, input).unwrap(); let result = parse(border_image::parse_value, input).unwrap();
assert_eq!(result.border_image_source, assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
parse_longhand!(border_image_source, "linear-gradient(red, blue)")); assert_longhand!(result, border_image_slice, "30 30% 45 fill");
assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill")); assert_longhand!(result, border_image_outset, "10px");
assert_eq!(result.border_image_outset, parse_longhand!(border_image_outset, "10px")); assert_longhand!(result, border_image_repeat, "round stretch");
assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round stretch")); assert_initial!(result, border_image_width);
assert_eq!(result.border_image_width, border_image_width::get_initial_specified_value());
} }
#[test] #[test]
@ -40,12 +51,11 @@ fn border_image_shorthand_should_parse_without_outset() {
let input = "linear-gradient(red, blue) 30 30% 45 fill / 20px 40px round"; let input = "linear-gradient(red, blue) 30 30% 45 fill / 20px 40px round";
let result = parse(border_image::parse_value, input).unwrap(); let result = parse(border_image::parse_value, input).unwrap();
assert_eq!(result.border_image_source, assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
parse_longhand!(border_image_source, "linear-gradient(red, blue)")); assert_longhand!(result, border_image_slice, "30 30% 45 fill");
assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill")); assert_longhand!(result, border_image_width, "20px 40px");
assert_eq!(result.border_image_width, parse_longhand!(border_image_width, "20px 40px")); assert_longhand!(result, border_image_repeat, "round");
assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round")); assert_initial!(result, border_image_outset);
assert_eq!(result.border_image_outset, border_image_outset::get_initial_specified_value());
} }
#[test] #[test]
@ -53,24 +63,22 @@ fn border_image_shorthand_should_parse_without_width_or_outset() {
let input = "linear-gradient(red, blue) 30 30% 45 fill round"; let input = "linear-gradient(red, blue) 30 30% 45 fill round";
let result = parse(border_image::parse_value, input).unwrap(); let result = parse(border_image::parse_value, input).unwrap();
assert_eq!(result.border_image_source, assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
parse_longhand!(border_image_source, "linear-gradient(red, blue)")); assert_longhand!(result, border_image_slice, "30 30% 45 fill");
assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill")); assert_longhand!(result, border_image_repeat, "round");
assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round")); assert_initial!(result, border_image_width);
assert_eq!(result.border_image_width, border_image_width::get_initial_specified_value()); assert_initial!(result, border_image_outset);
assert_eq!(result.border_image_outset, border_image_outset::get_initial_specified_value());
} }
#[test] #[test]
fn border_image_shorthand_should_parse_with_just_source() { fn border_image_shorthand_should_parse_with_just_source() {
let result = parse(border_image::parse_value, "linear-gradient(red, blue)").unwrap(); let result = parse(border_image::parse_value, "linear-gradient(red, blue)").unwrap();
assert_eq!(result.border_image_source, assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
parse_longhand!(border_image_source, "linear-gradient(red, blue)")); assert_initial!(result, border_image_slice);
assert_eq!(result.border_image_slice, border_image_slice::get_initial_specified_value()); assert_initial!(result, border_image_width);
assert_eq!(result.border_image_width, border_image_width::get_initial_specified_value()); assert_initial!(result, border_image_outset);
assert_eq!(result.border_image_outset, border_image_outset::get_initial_specified_value()); assert_initial!(result, border_image_repeat);
assert_eq!(result.border_image_repeat, border_image_repeat::get_initial_specified_value());
} }
#[test] #[test]

View file

@ -6,6 +6,10 @@ use style::properties;
size_of_test!(test_size_of_property_declaration, properties::PropertyDeclaration, 32); size_of_test!(test_size_of_property_declaration, properties::PropertyDeclaration, 32);
// This is huge, but we allocate it on the stack and then never move it,
// we only pass `&mut SourcePropertyDeclaration` references around.
size_of_test!(test_size_of_parsed_declaration, properties::SourcePropertyDeclaration, 576);
#[test] #[test]
fn size_of_specified_values() { fn size_of_specified_values() {
::style::properties::test_size_of_specified_values(); ::style::properties::test_size_of_specified_values();

View file

@ -21,6 +21,10 @@ fn size_of_selectors_dummy_types() {
size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32); size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32);
// This is huge, but we allocate it on the stack and then never move it,
// we only pass `&mut SourcePropertyDeclaration` references around.
size_of_test!(test_size_of_parsed_declaration, style::properties::SourcePropertyDeclaration, 704);
#[test] #[test]
fn size_of_specified_values() { fn size_of_specified_values() {
::style::properties::test_size_of_specified_values(); ::style::properties::test_size_of_specified_values();