servo: Merge #17288 - Make filter property animatable (from mantaroh:filter); r=hiro
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Sun, 18 Jun 2017 20:28:16 -0700
changeset 596531 ae924c659c7a028b5973c04f1c359f06c57b503f
parent 596530 2e0028f316115094552961b11681d9dd79e6e421
child 596532 7d056ddab0b25bfff1e8fdd3927538e5ad3f8fd3
push id64675
push usermak77@bonardo.net
push dateMon, 19 Jun 2017 12:08:05 +0000
reviewershiro
milestone56.0a1
servo: Merge #17288 - Make filter property animatable (from mantaroh:filter); r=hiro <!-- Please describe your changes on the following line: --> This is a PR for https://bugzilla.mozilla.org/show_bug.cgi?id=1362897 --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors <!-- Either: --> There are tests for these changes, a test case will be landed in web-platform-tests in https://bugzilla.mozilla.org/show_bug.cgi?id=1362897 <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: e41b7d06b473544755902be2c1334a7622c84940
servo/components/style/gecko/url.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
--- a/servo/components/style/gecko/url.rs
+++ b/servo/components/style/gecko/url.rs
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Common handling for the specified value CSS url() values.
 
 use gecko_bindings::structs::{ServoBundledURI, URLExtraData};
+use gecko_bindings::structs::mozilla::css::URLValueData;
 use gecko_bindings::structs::root::mozilla::css::ImageValue;
 use gecko_bindings::sugar::refptr::RefPtr;
 use parser::ParserContext;
 use std::fmt;
 use style_traits::{ToCss, ParseError};
 use stylearc::Arc;
 
 /// A specified url() value for gecko. Gecko does not eagerly resolve SpecifiedUrls.
@@ -46,16 +47,26 @@ impl SpecifiedUrl {
 
     /// Returns true if the URL is definitely invalid. We don't eagerly resolve
     /// URLs in gecko, so we just return false here.
     /// use its |resolved| status.
     pub fn is_invalid(&self) -> bool {
         false
     }
 
+    /// Convert from URLValueData to SpecifiedUrl.
+    pub unsafe fn from_url_value_data(url: &URLValueData)
+                                       -> Result<SpecifiedUrl, ()> {
+        Ok(SpecifiedUrl {
+            serialization: Arc::new(url.mString.to_string()),
+            extra_data: url.mExtraData.to_safe(),
+            image_value: None,
+        })
+    }
+
     /// Returns true if this URL looks like a fragment.
     /// See https://drafts.csswg.org/css-values/#local-urls
     pub fn is_fragment(&self) -> bool {
         self.as_str().chars().next().map_or(false, |c| c == '#')
     }
 
     /// Return the resolved url as string, or the empty string if it's invalid.
     ///
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3429,16 +3429,25 @@ fn static_assert() {
             } else {
                 Some(Au(self.gecko.mClip.x + self.gecko.mClip.width))
             };
 
             Either::First(ClipRect { top: top, right: right, bottom: bottom, left: left, })
         }
     }
 
+    <%
+    # This array is several filter function which has percentage or
+    # number value for function of clone / set.
+    # The setting / cloning process of other function(e.g. Blur / HueRotate) is
+    # different from these function. So this array don't include such function.
+    FILTER_FUNCTIONS = [ 'Brightness', 'Contrast', 'Grayscale', 'Invert',
+                         'Opacity', 'Saturate', 'Sepia' ]
+     %>
+
     pub fn set_filter(&mut self, v: longhands::filter::computed_value::T) {
         use properties::longhands::filter::computed_value::Filter::*;
         use gecko_bindings::structs::nsCSSShadowArray;
         use gecko_bindings::structs::nsStyleFilter;
         use gecko_bindings::structs::NS_STYLE_FILTER_BLUR;
         use gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS;
         use gecko_bindings::structs::NS_STYLE_FILTER_CONTRAST;
         use gecko_bindings::structs::NS_STYLE_FILTER_GRAYSCALE;
@@ -3455,75 +3464,112 @@ fn static_assert() {
         }
 
         unsafe {
             Gecko_ResetFilters(&mut self.gecko, v.filters.len());
         }
         debug_assert!(v.filters.len() == self.gecko.mFilters.len());
 
         for (servo, gecko_filter) in v.filters.into_iter().zip(self.gecko.mFilters.iter_mut()) {
-            //TODO: URL, drop-shadow
             match servo {
-                Blur(len)          => fill_filter(NS_STYLE_FILTER_BLUR,
-                                                  CoordDataValue::Coord(len.0),
-                                                  gecko_filter),
-                Brightness(factor) => fill_filter(NS_STYLE_FILTER_BRIGHTNESS,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Contrast(factor)   => fill_filter(NS_STYLE_FILTER_CONTRAST,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Grayscale(factor)  => fill_filter(NS_STYLE_FILTER_GRAYSCALE,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                HueRotate(angle)   => fill_filter(NS_STYLE_FILTER_HUE_ROTATE,
-                                                  CoordDataValue::from(angle),
-                                                  gecko_filter),
-                Invert(factor)     => fill_filter(NS_STYLE_FILTER_INVERT,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Opacity(factor)    => fill_filter(NS_STYLE_FILTER_OPACITY,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Saturate(factor)   => fill_filter(NS_STYLE_FILTER_SATURATE,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Sepia(factor)      => fill_filter(NS_STYLE_FILTER_SEPIA,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
+                % for func in FILTER_FUNCTIONS:
+                ${func}(factor) => fill_filter(NS_STYLE_FILTER_${func.upper()},
+                                               CoordDataValue::Factor(factor),
+                                               gecko_filter),
+                % endfor
+                Blur(length) => fill_filter(NS_STYLE_FILTER_BLUR,
+                                            CoordDataValue::Coord(length.0),
+                                            gecko_filter),
+
+                HueRotate(angle) => fill_filter(NS_STYLE_FILTER_HUE_ROTATE,
+                                                CoordDataValue::from(angle),
+                                                gecko_filter),
+
                 DropShadow(shadow) => {
                     gecko_filter.mType = NS_STYLE_FILTER_DROP_SHADOW;
 
                     fn init_shadow(filter: &mut nsStyleFilter) -> &mut nsCSSShadowArray {
                         unsafe {
                             let ref mut union = filter.__bindgen_anon_1;
                             let mut shadow_array: &mut *mut nsCSSShadowArray = union.mDropShadow.as_mut();
                             *shadow_array = Gecko_NewCSSShadowArray(1);
 
                             &mut **shadow_array
                         }
                     }
 
                     let mut gecko_shadow = init_shadow(gecko_filter);
                     gecko_shadow.mArray[0].set_from_shadow(shadow);
-                }
+                },
                 Url(ref url) => {
                     unsafe {
                         bindings::Gecko_nsStyleFilter_SetURLValue(gecko_filter, url.for_ffi());
                     }
-                }
+                },
             }
         }
     }
 
     pub fn copy_filter_from(&mut self, other: &Self) {
         unsafe {
             Gecko_CopyFiltersFrom(&other.gecko as *const _ as *mut _, &mut self.gecko);
         }
     }
+
+    pub fn clone_filter(&self) -> longhands::filter::computed_value::T {
+        use properties::longhands::filter::computed_value::Filter::*;
+        use values::specified::url::SpecifiedUrl;
+        use gecko_bindings::structs::NS_STYLE_FILTER_BLUR;
+        use gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS;
+        use gecko_bindings::structs::NS_STYLE_FILTER_CONTRAST;
+        use gecko_bindings::structs::NS_STYLE_FILTER_GRAYSCALE;
+        use gecko_bindings::structs::NS_STYLE_FILTER_INVERT;
+        use gecko_bindings::structs::NS_STYLE_FILTER_OPACITY;
+        use gecko_bindings::structs::NS_STYLE_FILTER_SATURATE;
+        use gecko_bindings::structs::NS_STYLE_FILTER_SEPIA;
+        use gecko_bindings::structs::NS_STYLE_FILTER_HUE_ROTATE;
+        use gecko_bindings::structs::NS_STYLE_FILTER_DROP_SHADOW;
+        use gecko_bindings::structs::NS_STYLE_FILTER_URL;
+
+        let mut filters = Vec::new();
+        for filter in self.gecko.mFilters.iter(){
+            match filter.mType {
+                % for func in FILTER_FUNCTIONS:
+                NS_STYLE_FILTER_${func.upper()} => {
+                    filters.push(${func}(
+                        GeckoStyleCoordConvertible::from_gecko_style_coord(
+                            &filter.mFilterParameter).unwrap()));
+                },
+                % endfor
+                NS_STYLE_FILTER_BLUR => {
+                    filters.push(Blur(Au::from_gecko_style_coord(
+                        &filter.mFilterParameter).unwrap()));
+                },
+                NS_STYLE_FILTER_HUE_ROTATE => {
+                    filters.push(HueRotate(
+                        GeckoStyleCoordConvertible::from_gecko_style_coord(
+                            &filter.mFilterParameter).unwrap()));
+                },
+                NS_STYLE_FILTER_DROP_SHADOW => {
+                    filters.push(unsafe {
+                        DropShadow((**filter.__bindgen_anon_1.mDropShadow.as_ref()).mArray[0].to_shadow())
+                    });
+                },
+                NS_STYLE_FILTER_URL => {
+                    filters.push(unsafe {
+                        (Url(SpecifiedUrl::from_url_value_data(
+                            &(**filter.__bindgen_anon_1.mURL.as_ref())._base).unwrap()))
+                    });
+                }
+                _ => {},
+            }
+        }
+        longhands::filter::computed_value::T::new(filters)
+    }
+
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedBox"
                   skip_longhands="image-orientation">
     // FIXME: Gecko uses a tricky way to store computed value of image-orientation
     //        within an u8. We could inline following glue codes by implementing all
     //        those tricky parts for Servo as well. But, it's not done yet just for
     //        convenience.
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -9,19 +9,22 @@
 use app_units::Au;
 use cssparser::{Parser, RGBA};
 use euclid::{Point2D, Size2D};
 #[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
 #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
+#[cfg(feature = "gecko")] use gecko::url::SpecifiedUrl;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
 use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
+use properties::longhands::filter::computed_value::Filter;
+use properties::longhands::filter::computed_value::T as Filters;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
 use properties::longhands::font_stretch::computed_value::T as FontStretch;
 use properties::longhands::text_shadow::computed_value::T as TextShadowList;
 use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use properties::longhands::transform::computed_value::T as TransformList;
 use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
@@ -931,19 +934,30 @@ impl Animatable for i32 {
         Ok((*self - *other).abs() as f64)
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-number
 impl Animatable for Angle {
     #[inline]
     fn add_weighted(&self, other: &Angle, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        self.radians()
-            .add_weighted(&other.radians(), self_portion, other_portion)
-            .map(Angle::from_radians)
+        match (*self, *other) {
+            % for angle_type in [ 'Degree', 'Gradian', 'Turn' ]:
+            (Angle::${angle_type}(val1), Angle::${angle_type}(val2)) => {
+                Ok(Angle::${angle_type}(
+                    try!(val1.add_weighted(&val2, self_portion, other_portion))
+                ))
+            }
+            % endfor
+            _ => {
+                self.radians()
+                    .add_weighted(&other.radians(), self_portion, other_portion)
+                    .map(Angle::from_radians)
+            }
+        }
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-percentage
 impl Animatable for Percentage {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Percentage((self.0 as f64 * self_portion + other.0 as f64 * other_portion) as f32))
@@ -3000,17 +3014,17 @@ impl Animatable for IntermediateSVGPaint
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 /// Intermediate type for box-shadow and text-shadow.
-/// The difference between normal shadow type is that this type uses
+/// The difference from normal shadow type is that this type uses
 /// IntermediateColor instead of ParserColor.
 pub struct IntermediateShadow {
     pub offset_x: Au,
     pub offset_y: Au,
     pub blur_radius: Au,
     pub spread_radius: Au,
     pub color: IntermediateColor,
     pub inset: bool,
@@ -3176,8 +3190,262 @@ impl Animatable for IntermediateShadowLi
         };
 
         result.extend(self.0.iter().cloned());
         result.extend(other.0.iter().cloned());
 
         Ok(IntermediateShadowList(result))
     }
 }
+
+/// Intermediate type for filter property.
+/// The difference from normal filter type is that this structure uses
+/// IntermediateColor into DropShadow's value.
+pub type IntermediateFilters = Vec<IntermediateFilter>;
+
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[allow(missing_docs)]
+pub enum IntermediateFilter {
+    Blur(Au),
+    Brightness(CSSFloat),
+    Contrast(CSSFloat),
+    Grayscale(CSSFloat),
+    HueRotate(Angle),
+    Invert(CSSFloat),
+    Opacity(CSSFloat),
+    Saturate(CSSFloat),
+    Sepia(CSSFloat),
+    % if product == "gecko":
+    DropShadow(IntermediateShadow),
+    Url(SpecifiedUrl),
+    % endif
+}
+
+impl From<Filters> for IntermediateFilters {
+    fn from(filters: Filters) -> IntermediateFilters {
+        filters.filters.into_iter().map(|f| f.into()).collect()
+    }
+}
+
+impl From<IntermediateFilters> for Filters {
+    fn from(filters: IntermediateFilters) -> Filters {
+        Filters::new(filters.into_iter().map(|f| f.into()).collect())
+    }
+}
+
+<%
+    FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
+                         'HueRotate', 'Invert', 'Opacity', 'Saturate',
+                         'Sepia' ]
+%>
+
+impl From<Filter> for IntermediateFilter {
+    fn from(filter: Filter) -> IntermediateFilter {
+        use properties::longhands::filter::computed_value::Filter::*;
+        match filter {
+            % for func in FILTER_FUNCTIONS:
+                ${func}(val) => IntermediateFilter::${func}(val),
+            % endfor
+            % if product == "gecko":
+                DropShadow(shadow) => {
+                    IntermediateFilter::DropShadow(shadow.into())
+                },
+                Url(ref url) => {
+                    IntermediateFilter::Url(url.clone())
+                },
+            % endif
+        }
+    }
+}
+
+impl From<IntermediateFilter> for Filter {
+    fn from(filter: IntermediateFilter) -> Filter {
+        match filter {
+            % for func in FILTER_FUNCTIONS:
+                IntermediateFilter::${func}(val) => Filter::${func}(val),
+            % endfor
+            % if product == "gecko":
+                IntermediateFilter::DropShadow(shadow) => {
+                    Filter::DropShadow(shadow.into())
+                },
+                IntermediateFilter::Url(ref url) => {
+                    Filter::Url(url.clone())
+                },
+            % endif
+        }
+    }
+}
+
+/// https://drafts.fxtf.org/filters/#animation-of-filters
+fn add_weighted_filter_function_impl(from: &IntermediateFilter,
+                                     to: &IntermediateFilter,
+                                     self_portion: f64,
+                                     other_portion: f64)
+                                     -> Result<IntermediateFilter, ()> {
+    match (from, to) {
+        % for func in [ 'Blur', 'HueRotate' ]:
+            (&IntermediateFilter::${func}(from_value),
+             &IntermediateFilter::${func}(to_value)) => {
+                Ok(IntermediateFilter::${func}(
+                    try!(from_value.add_weighted(&to_value,
+                                                 self_portion,
+                                                 other_portion))))
+           },
+        % endfor
+        % for func in [ 'Grayscale', 'Invert', 'Sepia' ]:
+            (&IntermediateFilter::${func}(from_value),
+             &IntermediateFilter::${func}(to_value)) => {
+                Ok(IntermediateFilter::${func}(try!(
+                    add_weighted_with_initial_val(&from_value,
+                                                  &to_value,
+                                                  self_portion,
+                                                  other_portion,
+                                                  &0.0))))
+            },
+        % endfor
+        % for func in [ 'Brightness', 'Contrast', 'Opacity', 'Saturate' ]:
+            (&IntermediateFilter::${func}(from_value),
+             &IntermediateFilter::${func}(to_value)) => {
+                Ok(IntermediateFilter::${func}(try!(
+                    add_weighted_with_initial_val(&from_value,
+                                                  &to_value,
+                                                  self_portion,
+                                                  other_portion,
+                                                  &1.0))))
+                },
+        % endfor
+        % if product == "gecko":
+            (&IntermediateFilter::DropShadow(from_value),
+             &IntermediateFilter::DropShadow(to_value)) => {
+                Ok(IntermediateFilter::DropShadow(try!(
+                    from_value.add_weighted(&to_value,
+                                            self_portion,
+                                            other_portion))))
+            },
+            (&IntermediateFilter::Url(_),
+             &IntermediateFilter::Url(_)) => {
+                Err(())
+            },
+        % endif
+        _ => {
+            // If specified the different filter functions,
+            // we will need to interpolate as discreate.
+            Err(())
+        },
+    }
+}
+
+/// https://drafts.fxtf.org/filters/#animation-of-filters
+fn add_weighted_filter_function(from: Option<<&IntermediateFilter>,
+                                to: Option<<&IntermediateFilter>,
+                                self_portion: f64,
+                                other_portion: f64) -> Result<IntermediateFilter, ()> {
+    match (from, to) {
+        (Some(f), Some(t)) => {
+            add_weighted_filter_function_impl(f, t, self_portion, other_portion)
+        },
+        (Some(f), None) => {
+            add_weighted_filter_function_impl(f, f, self_portion, 0.0)
+        },
+        (None, Some(t)) => {
+            add_weighted_filter_function_impl(t, t, other_portion, 0.0)
+        },
+        _ => { Err(()) }
+    }
+}
+
+fn compute_filter_square_distance(from: &IntermediateFilter,
+                                  to: &IntermediateFilter)
+                                  -> Result<f64, ()> {
+    match (from, to) {
+        % for func in FILTER_FUNCTIONS :
+            (&IntermediateFilter::${func}(f),
+             &IntermediateFilter::${func}(t)) => {
+                Ok(try!(f.compute_squared_distance(&t)))
+            },
+        % endfor
+        % if product == "gecko":
+            (&IntermediateFilter::DropShadow(f),
+             &IntermediateFilter::DropShadow(t)) => {
+                Ok(try!(f.compute_squared_distance(&t)))
+            },
+        % endif
+        _ => {
+            Err(())
+        }
+    }
+}
+
+impl Animatable for IntermediateFilters {
+    #[inline]
+    fn add_weighted(&self, other: &Self,
+                    self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        let mut filters: IntermediateFilters = Vec::new();
+        let mut from_iter = self.iter();
+        let mut to_iter = (&other).iter();
+
+        let mut from = from_iter.next();
+        let mut to = to_iter.next();
+        while (from,to) != (None, None) {
+            filters.push(try!(add_weighted_filter_function(from,
+                                                           to,
+                                                           self_portion,
+                                                           other_portion)));
+            if from != None {
+                from = from_iter.next();
+            }
+            if to != None {
+                to = to_iter.next();
+            }
+        }
+
+        Ok(filters)
+    }
+
+    fn add(&self, other: &Self) -> Result<Self, ()> {
+        let from_list = &self;
+        let to_list = &other;
+        let filters: IntermediateFilters =
+            vec![&from_list[..], &to_list[..]].concat();
+        Ok(filters)
+    }
+
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.compute_squared_distance(other).map(|sd| sd.sqrt())
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        let mut square_distance: f64 = 0.0;
+        let mut from_iter = self.iter();
+        let mut to_iter = (&other).iter();
+
+        let mut from = from_iter.next();
+        let mut to = to_iter.next();
+        while (from,to) != (None, None) {
+            let current_square_distance: f64 ;
+            if from == None {
+                let none = try!(add_weighted_filter_function(to, to, 0.0, 0.0));
+                current_square_distance =
+                    compute_filter_square_distance(&none, &(to.unwrap())).unwrap();
+
+                to = to_iter.next();
+            } else if to == None {
+                let none = try!(add_weighted_filter_function(from, from, 0.0, 0.0));
+                current_square_distance =
+                    compute_filter_square_distance(&none, &(from.unwrap())).unwrap();
+
+                from = from_iter.next();
+            } else {
+                current_square_distance =
+                    compute_filter_square_distance(&(from.unwrap()),
+                                                   &(to.unwrap())).unwrap();
+
+                from = from_iter.next();
+                to = to_iter.next();
+            }
+            square_distance += current_square_distance;
+        }
+        Ok(square_distance.sqrt())
+    }
+}
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -36,18 +36,17 @@
 ${helpers.predefined_type("clip",
                           "ClipRectOrAuto",
                           "computed::ClipRectOrAuto::auto()",
                           animation_value_type="ComputedValue",
                           boxed="True",
                           allow_quirks=True,
                           spec="https://drafts.fxtf.org/css-masking/#clip-property")}
 
-// FIXME: This prop should be animatable
-<%helpers:longhand name="filter" animation_value_type="none" extra_prefixes="webkit"
+<%helpers:longhand name="filter" animation_value_type="IntermediateFilters" extra_prefixes="webkit"
                    flags="CREATES_STACKING_CONTEXT FIXPOS_CB"
                    spec="https://drafts.fxtf.org/filters/#propdef-filter">
     //pub use self::computed_value::T as SpecifiedValue;
     use std::fmt;
     use style_traits::{HasViewportPercentage, ToCss};
     use values::CSSFloat;
     use values::specified::{Angle, Length};
     #[cfg(feature = "gecko")]