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`.
This commit is contained in:
Simon Sapin 2017-05-19 17:46:34 +02:00
parent e4f241389e
commit d2be5239f5
12 changed files with 356 additions and 303 deletions

8
Cargo.lock generated
View file

@ -1834,9 +1834,15 @@ name = "nodrop"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop-union 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop-union"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nom"
version = "1.2.4"
@ -2824,6 +2830,7 @@ name = "style"
version = "0.0.1"
dependencies = [
"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)",
"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)",
@ -3572,6 +3579,7 @@ dependencies = [
"checksum multistr 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "948d1285dd39981f6a5b1a72624c323312d29e2121682a742a87a773dd723bef"
"checksum net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)" = "18b9642ad6222faf5ce46f6966f59b71b9775ad5758c9e09fcf0a6c8061972b4"
"checksum nodrop 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbadd3f4c98dea0bd3d9b4be4c0cdaf1ab57035cb2e41fce3983db5add7cc5"
"checksum nodrop-union 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e31761704eb6a5f68237db6081e628ac8c3af34eb57fb27fcdea790f6685f01f"
"checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
"checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37"
"checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e"

View file

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

View file

@ -21,12 +21,13 @@ use_bindgen = ["bindgen", "regex", "toml"]
servo = ["serde", "serde_derive", "heapsize", "heapsize_derive",
"style_traits/servo", "servo_atoms", "servo_config", "html5ever",
"cssparser/heapsize", "cssparser/serde", "encoding", "smallvec/heapsizeof",
"rayon/unstable", "servo_url"]
"rayon/unstable", "servo_url", "arrayvec/use_union"]
testing = []
gecko_debug = ["nsstring_vendor/gecko_debug"]
[dependencies]
app_units = "0.4"
arrayvec = "0.3.20"
atomic_refcell = "0.1"
bitflags = "0.7"
bit-vec = "0.4.3"

View file

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

View file

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

View file

@ -202,14 +202,52 @@ impl PropertyDeclarationBlock {
}
/// 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 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) {
self.push_common(declaration, importance, false);
}
/// Implementation detail of push and ParsedDeclaration::expand*
pub fn push_common(&mut self, declaration: PropertyDeclaration, importance: Importance,
overwrite_more_important: bool) -> bool {
fn push_common(&mut self, declaration: PropertyDeclaration, importance: Importance,
overwrite_more_important: bool) -> bool {
let definitely_new = if let PropertyDeclarationId::Longhand(id) = declaration.id() {
!self.longhands.contains(id)
} else {
@ -657,15 +695,15 @@ pub fn parse_style_attribute(input: &str,
/// Parse a given property declaration. Can result in multiple
/// `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
pub fn parse_one_declaration(id: PropertyId,
input: &str,
url_data: &UrlExtraData,
error_reporter: &ParseErrorReporter,
parsing_mode: ParsingMode,
quirks_mode: QuirksMode)
-> Result<ParsedDeclaration, ()> {
/// This does not attempt to parse !important at all.
pub fn parse_one_declaration_into(declarations: &mut SourcePropertyDeclaration,
id: PropertyId,
input: &str,
url_data: &UrlExtraData,
error_reporter: &ParseErrorReporter,
parsing_mode: ParsingMode,
quirks_mode: QuirksMode)
-> Result<(), ()> {
let context = ParserContext::new(Origin::Author,
url_data,
error_reporter,
@ -673,7 +711,7 @@ pub fn parse_one_declaration(id: PropertyId,
parsing_mode,
quirks_mode);
Parser::new(input).parse_entirely(|parser| {
ParsedDeclaration::parse(id, &context, parser)
PropertyDeclaration::parse_into(declarations, id, &context, parser)
.map_err(|_| ())
})
}
@ -681,24 +719,23 @@ pub fn parse_one_declaration(id: PropertyId,
/// A struct to parse property declarations.
struct PropertyDeclarationParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
declarations: &'a mut SourcePropertyDeclaration,
}
/// Default methods reject all at rules.
impl<'a, 'b> AtRuleParser for PropertyDeclarationParser<'a, 'b> {
type Prelude = ();
type AtRule = (ParsedDeclaration, Importance);
type AtRule = Importance;
}
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)
-> Result<(ParsedDeclaration, Importance), ()> {
fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<Importance, ()> {
let id = try!(PropertyId::parse(name.into()));
let parsed = input.parse_until_before(Delimiter::Bang, |input| {
ParsedDeclaration::parse(id, self.context, input)
input.parse_until_before(Delimiter::Bang, |input| {
PropertyDeclaration::parse_into(self.declarations, id, self.context, input)
.map_err(|_| ())
})?;
let importance = match input.try(parse_important) {
@ -706,10 +743,8 @@ impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
Err(()) => Importance::Normal,
};
// In case there is still unparsed text in the declaration, we should roll back.
if !input.is_exhausted() {
return Err(())
}
Ok((parsed, importance))
input.expect_exhausted()?;
Ok(importance)
}
}
@ -719,15 +754,20 @@ impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
pub fn parse_property_declaration_list(context: &ParserContext,
input: &mut Parser)
-> PropertyDeclarationBlock {
let mut declarations = SourcePropertyDeclaration::new();
let mut block = PropertyDeclarationBlock::new();
let parser = PropertyDeclarationParser {
context: context,
declarations: &mut declarations,
};
let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() {
match declaration {
Ok((parsed, importance)) => parsed.expand_push_into(&mut block, importance),
Ok(importance) => {
block.extend(iter.parser.declarations.drain(), importance);
}
Err(range) => {
iter.parser.declarations.clear();
let pos = range.start;
let message = format!("Unsupported property declaration: '{}'",
iter.input.slice(range));

View file

@ -748,8 +748,8 @@
#[allow(unused_imports)]
use cssparser::Parser;
use parser::ParserContext;
use properties::{PropertyDeclaration, ParsedDeclaration, MaybeBoxed};
use properties::{ShorthandId, UnparsedValue, longhands};
use properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed};
use properties::{ShorthandId, LonghandId, UnparsedValue, longhands};
use std::fmt;
use stylearc::Arc;
use style_traits::ToCss;
@ -824,7 +824,8 @@
/// Parse the given shorthand and fill the result into the
/// `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();
let start = input.position();
let value = input.parse_entirely(|input| parse_value(context, input));
@ -833,17 +834,29 @@
}
let var = input.seen_var_functions();
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 {
input.reset(start);
let (first_token_type, css) = try!(
::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(),
first_token_type: first_token_type,
url_data: context.url_data.clone(),
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 {
Err(())
}

View file

@ -13,6 +13,7 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::fmt;
use std::mem;
use std::ops::Deref;
use stylearc::{Arc, UniqueArc};
@ -206,10 +207,11 @@ pub mod shorthands {
pub mod all {
use cssparser::Parser;
use parser::ParserContext;
use properties::{ParsedDeclaration, ShorthandId, UnparsedValue};
use properties::{SourcePropertyDeclaration, AllShorthand, ShorthandId, UnparsedValue};
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
// helpers:shorthand, but since the only values for the 'all'
// shorthand when not just a single CSS-wide keyword is one
@ -225,12 +227,13 @@ pub mod shorthands {
input.reset(start);
let (first_token_type, css) = try!(
::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(),
first_token_type: first_token_type,
url_data: context.url_data.clone(),
from_shorthand: Some(ShorthandId::All),
})))
}));
Ok(())
} else {
Err(())
}
@ -1001,216 +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}(
${sub_property.ident}
),
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.
#[derive(PartialEq, Clone)]
pub enum PropertyDeclaration {
@ -1504,6 +1297,180 @@ impl PropertyDeclaration {
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")]

View file

@ -6,7 +6,7 @@
use cssparser::{parse_important, Parser, Token};
use parser::ParserContext;
use properties::{PropertyId, ParsedDeclaration};
use properties::{PropertyId, PropertyDeclaration, SourcePropertyDeclaration};
use std::fmt;
use style_traits::ToCss;
use stylesheets::CssRuleType;
@ -213,7 +213,8 @@ impl Declaration {
};
let mut input = Parser::new(&self.val);
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);
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::parallel;
use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
use style::properties::{CascadeFlags, ComputedValues, Importance, ParsedDeclaration, StyleBuilder};
use style::properties::{LonghandIdSet, PropertyDeclarationBlock, PropertyId};
use style::properties::{CascadeFlags, ComputedValues, Importance, SourcePropertyDeclaration};
use style::properties::{LonghandIdSet, PropertyDeclarationBlock, PropertyId, StyleBuilder};
use style::properties::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP;
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::rule_tree::StyleSource;
use style::selector_parser::PseudoElementCascadeType;
@ -1252,22 +1252,25 @@ pub extern "C" fn Servo_StyleSet_Drop(data: RawServoStyleSetOwned) {
let _ = data.into_box::<PerDocumentStyleData>();
}
fn parse_property(property_id: PropertyId,
value: *const nsACString,
data: *mut URLExtraData,
parsing_mode: structs::ParsingMode,
quirks_mode: QuirksMode) -> Result<ParsedDeclaration, ()> {
fn parse_property_into(declarations: &mut SourcePropertyDeclaration,
property_id: PropertyId,
value: *const nsACString,
data: *mut URLExtraData,
parsing_mode: structs::ParsingMode,
quirks_mode: QuirksMode) -> Result<(), ()> {
use style::parser::ParsingMode;
let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
let url_data = unsafe { RefPtr::from_ptr_ref(&data) };
let parsing_mode = ParsingMode::from_bits_truncate(parsing_mode);
parse_one_declaration(property_id,
value,
url_data,
&RustLogReporter,
parsing_mode,
quirks_mode)
parse_one_declaration_into(
declarations,
property_id,
value,
url_data,
&RustLogReporter,
parsing_mode,
quirks_mode)
}
#[no_mangle]
@ -1278,11 +1281,13 @@ pub extern "C" fn Servo_ParseProperty(property: nsCSSPropertyID, value: *const n
-> RawServoDeclarationBlockStrong {
let id = get_property_id_from_nscsspropertyid!(property,
RawServoDeclarationBlockStrong::null());
match parse_property(id, value, data, parsing_mode, quirks_mode.into()) {
Ok(parsed) => {
let mut declarations = SourcePropertyDeclaration::new();
match parse_property_into(&mut declarations, id, value, data,
parsing_mode, quirks_mode.into()) {
Ok(()) => {
let global_style_data = &*GLOBAL_STYLE_DATA;
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()
}
Err(_) => RawServoDeclarationBlockStrong::null()
@ -1439,11 +1444,13 @@ fn set_property(declarations: RawServoDeclarationBlockBorrowed, property_id: Pro
value: *const nsACString, is_important: bool, data: *mut URLExtraData,
parsing_mode: structs::ParsingMode,
quirks_mode: QuirksMode) -> bool {
match parse_property(property_id, value, data, parsing_mode, quirks_mode) {
Ok(parsed) => {
let mut source_declarations = SourcePropertyDeclaration::new();
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 };
write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
parsed.expand_set_into(decls, importance)
decls.extend_reset(source_declarations.drain(), importance)
})
},
Err(_) => false,
@ -1968,11 +1975,15 @@ pub extern "C" fn Servo_CSSSupports2(property: *const nsACString,
value: *const nsACString) -> bool {
let id = get_property_id_from_property!(property, false);
parse_property(id,
value,
unsafe { DUMMY_URL_DATA },
structs::ParsingMode_Default,
QuirksMode::NoQuirks).is_ok()
let mut declarations = SourcePropertyDeclaration::new();
parse_property_into(
&mut declarations,
id,
value,
unsafe { DUMMY_URL_DATA },
structs::ParsingMode_Default,
QuirksMode::NoQuirks
).is_ok()
}
#[no_mangle]

View file

@ -5,7 +5,10 @@
use style::properties;
size_of_test!(test_size_of_property_declaration, properties::PropertyDeclaration, 32);
size_of_test!(test_size_of_parsed_declaration, properties::ParsedDeclaration, 272);
// 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]
fn size_of_specified_values() {

View file

@ -20,7 +20,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_parsed_declaration, style::properties::ParsedDeclaration, 400);
// 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]
fn size_of_specified_values() {