diff --git a/components/style/properties/data.py b/components/style/properties/data.py index b9b17860dcf..d6aa4a016ad 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -46,6 +46,11 @@ def to_camel_case_lower(ident): return camel[0].lower() + camel[1:] +# https://drafts.csswg.org/cssom/#css-property-to-idl-attribute +def to_idl_name(ident): + return re.sub("-([a-z])", lambda m: m.group(1).upper(), ident) + + def parse_aliases(value): aliases = {} for pair in value.split(): diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index b32dd84fa3f..5f00342895c 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -4,7 +4,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import SYSTEM_FONT_LONGHANDS %> +<% from data import to_idl_name, SYSTEM_FONT_LONGHANDS %> use app_units::Au; use cssparser::{Parser, RGBA}; @@ -23,7 +23,8 @@ use properties::longhands::transform::computed_value::ComputedOperation as Trans use properties::longhands::transform::computed_value::T as TransformList; use properties::longhands::vertical_align::computed_value::T as VerticalAlign; use properties::longhands::visibility::computed_value::T as Visibility; -#[cfg(feature = "gecko")] use properties::{PropertyDeclarationId, LonghandId}; +#[cfg(feature = "gecko")] use properties::{PropertyId, PropertyDeclarationId, LonghandId}; +#[cfg(feature = "gecko")] use properties::{ShorthandId}; use selectors::parser::SelectorParseError; use smallvec::SmallVec; use std::cmp; @@ -3204,3 +3205,56 @@ impl Animatable for AnimatedFilterList { Ok(square_distance.sqrt()) } } + +/// A comparator to sort PropertyIds such that longhands are sorted before shorthands, +/// shorthands with fewer components are sorted before shorthands with more components, +/// and otherwise shorthands are sorted by IDL name as defined by [Web Animations][property-order]. +/// +/// Using this allows us to prioritize values specified by longhands (or smaller +/// shorthand subsets) when longhands and shorthands are both specified on the one keyframe. +/// +/// Example orderings that result from this: +/// +/// margin-left, margin +/// +/// and: +/// +/// border-top-color, border-color, border-top, border +/// +/// [property-order] https://w3c.github.io/web-animations/#calculating-computed-keyframes +#[cfg(feature = "gecko")] +pub fn compare_property_priority(a: &PropertyId, b: &PropertyId) -> cmp::Ordering { + match (a.as_shorthand(), b.as_shorthand()) { + // Within shorthands, sort by the number of subproperties, then by IDL name. + (Ok(a), Ok(b)) => { + let subprop_count_a = a.longhands().len(); + let subprop_count_b = b.longhands().len(); + subprop_count_a.cmp(&subprop_count_b).then_with( + || get_idl_name_sort_order(&a).cmp(&get_idl_name_sort_order(&b))) + }, + + // Longhands go before shorthands. + (Ok(_), Err(_)) => cmp::Ordering::Greater, + (Err(_), Ok(_)) => cmp::Ordering::Less, + + // Both are longhands or custom properties in which case they don't overlap and should + // sort equally. + _ => cmp::Ordering::Equal, + } +} + +#[cfg(feature = "gecko")] +fn get_idl_name_sort_order(shorthand: &ShorthandId) -> u32 { +<% +# Sort by IDL name. +sorted_shorthands = sorted(data.shorthands, key=lambda p: to_idl_name(p.ident)) + +# Annotate with sorted position +sorted_shorthands = [(p, position) for position, p in enumerate(sorted_shorthands)] +%> + match *shorthand { + % for property, position in sorted_shorthands: + ShorthandId::${property.camel_case} => ${position}, + % endfor + } +} diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 028f8ee8d3f..bdbf856203e 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -83,6 +83,7 @@ use style::gecko_bindings::structs::nsCSSValueSharedList; use style::gecko_bindings::structs::nsCompatibility; use style::gecko_bindings::structs::nsIDocument; use style::gecko_bindings::structs::nsStyleTransformMatrix::MatrixTransformOperator; +use style::gecko_bindings::structs::nsTArray; use style::gecko_bindings::structs::nsresult; use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, HasBoxFFI}; use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong}; @@ -94,9 +95,10 @@ use style::parallel; use style::parser::ParserContext; use style::properties::{ComputedValues, Importance}; use style::properties::{IS_FIELDSET_CONTENT, LonghandIdSet}; -use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyId}; +use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyId, ShorthandId}; use style::properties::{SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, SourcePropertyDeclaration, StyleBuilder}; use style::properties::animated_properties::{Animatable, AnimatableLonghand, AnimationValue}; +use style::properties::animated_properties::compare_property_priority; use style::properties::parse_one_declaration_into; use style::rule_tree::StyleSource; use style::selector_parser::PseudoElementCascadeType; @@ -2916,6 +2918,53 @@ fn create_context<'a>( } } +struct PropertyAndIndex { + property: PropertyId, + index: usize, +} + +struct PrioritizedPropertyIter<'a> { + properties: &'a nsTArray, + sorted_property_indices: Vec, + curr: usize, +} + +impl<'a> PrioritizedPropertyIter<'a> { + pub fn new(properties: &'a nsTArray) -> PrioritizedPropertyIter { + // If we fail to convert a nsCSSPropertyID into a PropertyId we shouldn't fail outright + // but instead by treating that property as the 'all' property we make it sort last. + let all = PropertyId::Shorthand(ShorthandId::All); + + let mut sorted_property_indices: Vec = + properties.iter().enumerate().map(|(index, pair)| { + PropertyAndIndex { + property: PropertyId::from_nscsspropertyid(pair.mProperty) + .unwrap_or(all.clone()), + index, + } + }).collect(); + sorted_property_indices.sort_by(|a, b| compare_property_priority(&a.property, &b.property)); + + PrioritizedPropertyIter { + properties, + sorted_property_indices, + curr: 0, + } + } +} + +impl<'a> Iterator for PrioritizedPropertyIter<'a> { + type Item = &'a PropertyValuePair; + + fn next(&mut self) -> Option<&'a PropertyValuePair> { + if self.curr >= self.sorted_property_indices.len() { + return None + } + self.curr += 1; + Some(&self.properties[self.sorted_property_indices[self.curr - 1].index]) + } +} + #[no_mangle] pub extern "C" fn Servo_GetComputedKeyframeValues(keyframes: RawGeckoKeyframeListBorrowed, element: RawGeckoElementBorrowed, @@ -2946,9 +2995,8 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(keyframes: RawGeckoKeyframeLis let mut seen = LonghandIdSet::new(); - let iter = keyframe.mPropertyValues.iter(); let mut property_index = 0; - for property in iter { + for property in PrioritizedPropertyIter::new(&keyframe.mPropertyValues) { if simulate_compute_values_failure(property) { continue; }